diff --git a/doc/Images/Banner_App.png b/doc/Images/Banner_App.png index 09b4cbb..739cd15 100644 Binary files a/doc/Images/Banner_App.png and b/doc/Images/Banner_App.png differ diff --git a/src/Api/src/controllers/spotifyController.ts b/src/Api/src/controllers/spotifyController.ts index c645f13..595bad0 100644 --- a/src/Api/src/controllers/spotifyController.ts +++ b/src/Api/src/controllers/spotifyController.ts @@ -1,3 +1,4 @@ + import IController from './interfaces/IController'; import { Router, Request, Response, NextFunction } from 'express'; import HttpException from '../exception/HttpException'; @@ -50,41 +51,39 @@ class SpotifyController implements IController { res: Response, next: NextFunction ): Promise => { - try { - const params = req.query.refresh_token; - if (!req.query.refresh_token) { - return res.json({ - "error": "Parameter refresh_token missing" - }); - } - let authOptions = { - method: 'POST', - url: 'https://accounts.spotify.com/api/token', - data: qs.stringify({ - grant_type: 'refresh_token', - refresh_token: params - }), - headers: { - 'Authorization': 'Basic ' + (Buffer.from(this.CLIENT_ID_SPOTIFY + ':' + this.CLIENT_SECRET_SPOTIFY).toString('base64')), - 'Content-Type': 'application/x-www-form-urlencoded' - }, - json: true - }; - - axios(authOptions) - .then(session => { - if (session.status === 200) { - res.send({ - "access_token": session.data.access_token, - "refresh_token": session.data.refresh_token, - "expires_in": session.data.expires_in - }); - } - }); - } catch (error) { - next(new HttpException(400, 'Cannot get a new refresh token')); + const params = req.query.refresh_token; + if (!req.query.refresh_token) { + return res.json({ + "error": "Parameter refresh_token missing" + }); } + let authOptions = { + method: 'POST', + url: 'https://accounts.spotify.com/api/token', + data: qs.stringify({ + grant_type: 'refresh_token', + refresh_token: params + }), + headers: { + 'Authorization': 'Basic ' + (Buffer.from(this.CLIENT_ID_SPOTIFY + ':' + this.CLIENT_SECRET_SPOTIFY).toString('base64')), + 'Content-Type': 'application/x-www-form-urlencoded' + }, + json: true + }; + axios(authOptions) + .then(session => { + if (session.status === 200) { + res.send({ + "access_token": session.data.access_token, + "refresh_token": session.data.refresh_token, + "expires_in": session.data.expires_in + }); + } + }) + .catch(error => { + res.status(400).send("Cannot get a new refresh token"); + }); } private getAccessToken = async ( @@ -93,7 +92,7 @@ class SpotifyController implements IController { next: NextFunction ): Promise => { let code = req.query.code; - let storedRedirectUri = req.cookies ? req.cookies[this.clientRedirect] : null; + let storedRedirectUri = req.cookies ? req.cookies[this.clientRedirect] : null; var authOptions = { method: 'POST', url: this.API_URL, @@ -123,7 +122,6 @@ class SpotifyController implements IController { })); } } catch (error) { - console.log(error); next(new HttpException(400, 'Error connection: ' + error.message)); } }; diff --git a/src/Api/src/controllers/userController.ts b/src/Api/src/controllers/userController.ts index d34d1eb..3f7ede1 100644 --- a/src/Api/src/controllers/userController.ts +++ b/src/Api/src/controllers/userController.ts @@ -7,9 +7,11 @@ import validator from '../middlewares/UserValidation' import validationMiddleware from '../middlewares/validationMiddleware'; import authenticator from '../middlewares/authMiddleware' import LocationService from '../services/LocationService'; +import axios, { AxiosError } from 'axios'; class UserController implements IController { - public path = '/users'; + public path = '/user'; + public authPath = '/auth'; public router = Router(); private userService = new UserService(); private locationService = new LocationService(); @@ -20,16 +22,17 @@ class UserController implements IController { private initRoutes(): void { this.router.post( - `${this.path}/register`, + `${this.authPath}/register`, validationMiddleware(validator.register), this.register ); this.router.post( - `${this.path}/login`, + `${this.authPath}/login`, validationMiddleware(validator.login), this.login ); this.router.get(`${this.path}`, authenticator, this.getUser); + this.router.delete(`${this.path}`, authenticator, this.deleteUser); this.router.get(`${this.path}/nextTo`, authenticator, this.getUserNext); } @@ -38,17 +41,59 @@ class UserController implements IController { res: Response, next: NextFunction ): Promise => { + + let access_token; + let idSpotify: string; + let image: string; + const { name, email, password, tokenSpotify } = req.body; + const apiBaseUrl = process.env.API_BASE_URL || 'http://localhost:8080/api'; + const refreshUrl = `${apiBaseUrl}/spotify/refresh?refresh_token=${tokenSpotify}`; + try { + var authOptions = { + method: 'GET', + url: refreshUrl, + json: true + }; + const authResponse = await axios(authOptions); + if (authResponse.status === 200) { + access_token = authResponse.data.access_token; + const headers = { + Authorization: `Bearer ${access_token}`, + }; + const resp = await axios.get('https://api.spotify.com/v1/me', { headers }); + if (resp.status == 200) { + const images = resp.data.images; + idSpotify = resp.data.id; + if (images && images.length > 0) { + images.sort((a: any, b: any) => b.height - a.height); + image = images[0].url; + } + else { + image = "data:image/png;base64,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" + } + } + } + } catch (error: any) { + if (error.response.status === 400) { + res.status(401).send("Unauthorized: Spotify token is invalid"); + return; + } + res.status(500).send("Internal Server Error: Unable to authenticate with Spotify"); + return; + } + try { - const { name, email, password, idSpotify } = req.body; const token = await this.userService.register( name.toLowerCase(), email.toLowerCase(), password, - idSpotify + idSpotify, + tokenSpotify, + image ); res.status(201).json({ token }); } catch (error: any) { - next(new HttpException(400, error.message)); + next(new HttpException(409, error.message)); } }; @@ -71,12 +116,23 @@ class UserController implements IController { res: Response, next: NextFunction ): Response | void => { - if (!req.user) { - return next(new HttpException(404, 'No logged in user')); - } res.status(200).send({ data: req.user }); }; + private deleteUser = async ( + req: Request, + res: Response, + next: NextFunction + ): Promise => { + try { + const { _id } = req.user; + await this.userService.delete(_id); + res.status(204).send(); + } catch (error: any) { + next(new HttpException(404, error.message)); + } + }; + private getUserNext = async ( req: Request, res: Response, @@ -99,7 +155,6 @@ class UserController implements IController { } } } - export default UserController; declare global { diff --git a/src/Api/src/database/UserSchema.ts b/src/Api/src/database/UserSchema.ts index 918ee40..447f71b 100644 --- a/src/Api/src/database/UserSchema.ts +++ b/src/Api/src/database/UserSchema.ts @@ -8,6 +8,10 @@ const userSchema = new Schema({ required: true, unique: true }, + tokenSpotify: { + type: String, + required: true + }, name: { type: String, required: true, @@ -23,6 +27,11 @@ const userSchema = new Schema({ type: String, required: true } + , + image: { + type: String, + required: true + } }, { timestamps: true } ); diff --git a/src/Api/src/middlewares/UserValidation.ts b/src/Api/src/middlewares/UserValidation.ts index 2678453..256a289 100644 --- a/src/Api/src/middlewares/UserValidation.ts +++ b/src/Api/src/middlewares/UserValidation.ts @@ -1,10 +1,11 @@ import Joi from 'joi'; const register = Joi.object({ - name: Joi.string().max(30).required(), + name: Joi.string().max(30).required().regex(/^[a-zA-Z0-9_]+$/) + .message("Name should only contain alphanumeric characters (letters, numbers, and underscores)"), email: Joi.string().email().required(), password: Joi.string().min(6).required(), - idSpotify: Joi.string(), + tokenSpotify: Joi.string().required() }); const login = Joi.object({ diff --git a/src/Api/src/services/UserService.ts b/src/Api/src/services/UserService.ts index c0afc9d..d853301 100644 --- a/src/Api/src/services/UserService.ts +++ b/src/Api/src/services/UserService.ts @@ -1,21 +1,27 @@ +import LocationSchema from "../database/LocationSchema"; import UserSchema from "../database/UserSchema"; import token from "./TokenService"; class UserService { private user = UserSchema; + private location = LocationSchema; public async register( name: string, email: string, password: string, - idSpotify: string + idSpotify: string, + tokenSpotify: string, + image: string ): Promise { try { const user = await this.user.create({ name, email, password, - idSpotify + tokenSpotify, + idSpotify, + image }); return token.createToken(user); } catch (error: any) { @@ -37,6 +43,18 @@ class UserService { throw new Error('Wrong credentials given'); } } + + public async delete( + id: string + ): Promise { + try { + await this.user.findByIdAndRemove(id); + await this.location.findByIdAndRemove(id); + return; + } catch (error: any) { + throw new Error(error.message); + } + } } export default UserService; \ No newline at end of file diff --git a/src/FLAD/App.tsx b/src/FLAD/App.tsx index 444c82f..2d797b6 100644 --- a/src/FLAD/App.tsx +++ b/src/FLAD/App.tsx @@ -7,8 +7,7 @@ SplashScreen.preventAutoHideAsync(); export default function App() { return ( - + ); -} - +} \ No newline at end of file diff --git a/src/FLAD/assets/images/chevron_left_icon.png b/src/FLAD/assets/images/chevron_left_icon.png index a9a6110..c13af20 100644 Binary files a/src/FLAD/assets/images/chevron_left_icon.png and b/src/FLAD/assets/images/chevron_left_icon.png differ diff --git a/src/FLAD/assets/images/chevron_right_icon.png b/src/FLAD/assets/images/chevron_right_icon.png index 91a7086..3fba365 100644 Binary files a/src/FLAD/assets/images/chevron_right_icon.png and b/src/FLAD/assets/images/chevron_right_icon.png differ diff --git a/src/FLAD/assets/images/flady.png b/src/FLAD/assets/images/flady.png index 172ff00..bd520c2 100644 Binary files a/src/FLAD/assets/images/flady.png and b/src/FLAD/assets/images/flady.png differ diff --git a/src/FLAD/assets/images/flady_angry.png b/src/FLAD/assets/images/flady_angry.png index da322f7..5cb25dc 100644 Binary files a/src/FLAD/assets/images/flady_angry.png and b/src/FLAD/assets/images/flady_angry.png differ diff --git a/src/FLAD/assets/images/flady_cry.png b/src/FLAD/assets/images/flady_cry.png index e7079fb..5f3744e 100644 Binary files a/src/FLAD/assets/images/flady_cry.png and b/src/FLAD/assets/images/flady_cry.png differ diff --git a/src/FLAD/assets/images/flady_icon.png b/src/FLAD/assets/images/flady_icon.png index c6c3b40..037e4f1 100644 Binary files a/src/FLAD/assets/images/flady_icon.png and b/src/FLAD/assets/images/flady_icon.png differ diff --git a/src/FLAD/assets/images/flady_love.png b/src/FLAD/assets/images/flady_love.png index c03e03d..53a2fd8 100644 Binary files a/src/FLAD/assets/images/flady_love.png and b/src/FLAD/assets/images/flady_love.png differ diff --git a/src/FLAD/assets/images/flady_shadow.png b/src/FLAD/assets/images/flady_shadow.png index f4c532e..7fa3992 100644 Binary files a/src/FLAD/assets/images/flady_shadow.png and b/src/FLAD/assets/images/flady_shadow.png differ diff --git a/src/FLAD/assets/images/flady_star.png b/src/FLAD/assets/images/flady_star.png index 01dc134..792e071 100644 Binary files a/src/FLAD/assets/images/flady_star.png and b/src/FLAD/assets/images/flady_star.png differ diff --git a/src/FLAD/assets/images/ok_icon.png b/src/FLAD/assets/images/ok_icon.png new file mode 100644 index 0000000..7a2927b Binary files /dev/null and b/src/FLAD/assets/images/ok_icon.png differ diff --git a/src/FLAD/assets/images/profil_icon_asupp.png b/src/FLAD/assets/images/profil_icon_asupp.png deleted file mode 100644 index b9c6183..0000000 Binary files a/src/FLAD/assets/images/profil_icon_asupp.png and /dev/null differ diff --git a/src/FLAD/assets/images/spotify_icon.png b/src/FLAD/assets/images/spotify_icon.png index 011863b..3c62ae8 100644 Binary files a/src/FLAD/assets/images/spotify_icon.png and b/src/FLAD/assets/images/spotify_icon.png differ diff --git a/src/FLAD/components/Artist.tsx b/src/FLAD/components/Artist.tsx index 3bf585c..8c7a357 100644 --- a/src/FLAD/components/Artist.tsx +++ b/src/FLAD/components/Artist.tsx @@ -5,7 +5,7 @@ import Animated, { ZoomOut, } from "react-native-reanimated"; import { Feather as Icon } from "@expo/vector-icons"; -import Music from "../model/Music"; +import Music from "../models/Music"; import { useState } from "react"; const { width } = Dimensions.get("window"); diff --git a/src/FLAD/components/ArtistChip.tsx b/src/FLAD/components/ArtistChip.tsx index ce5a8b8..f20f00e 100644 --- a/src/FLAD/components/ArtistChip.tsx +++ b/src/FLAD/components/ArtistChip.tsx @@ -1,6 +1,6 @@ import { useCallback } from 'react'; import { View, Text, Image, Pressable, Linking, Alert } from 'react-native' -import Artist from '../model/Artist'; +import Artist from '../models/Artist'; interface ArtistChipProps { backgroundColor: string; diff --git a/src/FLAD/components/Card.tsx b/src/FLAD/components/Card.tsx index 87b5f2f..72523aa 100644 --- a/src/FLAD/components/Card.tsx +++ b/src/FLAD/components/Card.tsx @@ -133,7 +133,6 @@ const Card = ({ image, onSwipe }: CardProps) => { return ( - { +export default function FladInput({ background, foreground, progress }: CircularProps) { const theta = multiply(progress, 2 * PI); const opacity = lessThan(theta, PI); @@ -26,10 +26,9 @@ const FladInput = ({ background, foreground, progress }: CircularProps) => { - + + ); -}; - -export default FladInput; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/FLAD/components/FriendComponent.tsx b/src/FLAD/components/FriendComponent.tsx index bc06b0c..78015fd 100644 --- a/src/FLAD/components/FriendComponent.tsx +++ b/src/FLAD/components/FriendComponent.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { StyleSheet, Text, View, Image } from 'react-native'; -import { color } from 'react-native-reanimated'; import { useSelector } from 'react-redux'; import { colorsDark } from '../constants/colorsDark'; import { colorsLight } from '../constants/colorsLight'; diff --git a/src/FLAD/components/HalfCircle.tsx b/src/FLAD/components/HalfCircle.tsx new file mode 100644 index 0000000..5469a32 --- /dev/null +++ b/src/FLAD/components/HalfCircle.tsx @@ -0,0 +1,20 @@ +import { View } from 'react-native' + +interface HalfCirlceProps { + backgroundColor: string; +} + +export default function HalfCirlce({ backgroundColor }: HalfCirlceProps) { + return ( + + + + + + ); +}; diff --git a/src/FLAD/components/PaginatorComponent.tsx b/src/FLAD/components/PaginatorComponent.tsx index d2d7c12..62e66e3 100644 --- a/src/FLAD/components/PaginatorComponent.tsx +++ b/src/FLAD/components/PaginatorComponent.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { View, StyleSheet, Animated, useWindowDimensions } from 'react-native'; - import normalize from './Normalize'; // @ts-ignore diff --git a/src/FLAD/components/PlayButtonComponent.tsx b/src/FLAD/components/PlayButtonComponent.tsx deleted file mode 100644 index 0558e58..0000000 --- a/src/FLAD/components/PlayButtonComponent.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Image, StyleSheet, Pressable } from 'react-native' -import React from 'react' -import Icons from '../assets/icon'; -import { RiveRef } from 'rive-react-native'; - -export default function PlayButtonComponent() { - const riveRef = React.useRef(null); - - const handlePlay = () => { riveRef.current?.play() }; - return ( - - - - ); -}; - -const styles = StyleSheet.create({ - button: { - justifyContent: 'center', - alignItems: 'center', - }, - image: { - borderRadius: 24, - resizeMode: 'cover', - width: 65, - height: 65, - backgroundColor: 'black', - } -}) \ No newline at end of file diff --git a/src/FLAD/data/data.ts b/src/FLAD/data/data.ts index 570e4b2..867a55b 100644 --- a/src/FLAD/data/data.ts +++ b/src/FLAD/data/data.ts @@ -1,5 +1,5 @@ -import Music from "../model/Music"; -import { Spot } from "../model/Spot"; +import Music from "../models/Music"; +import { Spot } from "../models/Spot"; export const spotsData: Spot[] = [ new Spot("1", new Music("6KNw3UKRp3QRsO7Cf4ASVE", diff --git a/src/FLAD/data/slides.tsx b/src/FLAD/data/slides.tsx index 0104654..a55b6bc 100644 --- a/src/FLAD/data/slides.tsx +++ b/src/FLAD/data/slides.tsx @@ -8,7 +8,7 @@ export default [ { id: '2', title: 'Tous les jours de nouvelles musiques qui peuvent vous plaire', - description: 'L\'application apprends de vous et de vos amis pour vous suggérer des albums et des musics', + description: 'L\'application apprend de vous et de vos amis pour vous suggérer des albums et des musiques', image: require('../assets/images/board_2.png') }, { diff --git a/src/FLAD/model/mapper/UserMapper.ts b/src/FLAD/model/mapper/UserMapper.ts deleted file mode 100644 index 30f1b31..0000000 --- a/src/FLAD/model/mapper/UserMapper.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { User } from "../User"; - -export class UserMapper { - - public static toModel(user: any): User { - return new User(user.idFlad, user.idSpotify, user.email, user.createdAt, user.name, user.imageUrl); - } -} \ No newline at end of file diff --git a/src/FLAD/model/Artist.ts b/src/FLAD/models/Artist.ts similarity index 100% rename from src/FLAD/model/Artist.ts rename to src/FLAD/models/Artist.ts diff --git a/src/FLAD/model/Music.ts b/src/FLAD/models/Music.ts similarity index 100% rename from src/FLAD/model/Music.ts rename to src/FLAD/models/Music.ts diff --git a/src/FLAD/model/Spot.ts b/src/FLAD/models/Spot.ts similarity index 100% rename from src/FLAD/model/Spot.ts rename to src/FLAD/models/Spot.ts diff --git a/src/FLAD/model/User.ts b/src/FLAD/models/User.ts similarity index 55% rename from src/FLAD/model/User.ts rename to src/FLAD/models/User.ts index e5c3cb8..57bac03 100644 --- a/src/FLAD/model/User.ts +++ b/src/FLAD/models/User.ts @@ -1,33 +1,33 @@ export class User { - private _idFlad: string; + private _id: string; private _idSpotify: string; - private _email: string; - private _createdAt: Date; private _name: string; + private _email: string; + private _creationDate: Date; public image: string; - constructor(idFlad: string, idSpotify: string, email: string, createdAt: Date, name: string, image: string) { - this._name = name; - this._idFlad = idFlad; + constructor(id: string, idSpotify: string, name: string, email: string, creationDate: Date, image: string) { + this._id = id; this._idSpotify = idSpotify; - this._createdAt = createdAt; + this._name = name; this._email = email; + this._creationDate = creationDate; this.image = image; } - get idFlad(): string { - return this._idFlad; + get id(): string { + return this._id; } get idSpotify(): string { return this._idSpotify; } + get name(): string { + return this._name; + } get email(): string { return this._email; } - get createAt(): Date { - return this._createdAt; - } - get name(): string { - return this._name; + get creationDate(): Date { + return this._creationDate; } } \ No newline at end of file diff --git a/src/FLAD/model/mapper/MusicMapper.ts b/src/FLAD/models/mapper/MusicMapper.ts similarity index 100% rename from src/FLAD/model/mapper/MusicMapper.ts rename to src/FLAD/models/mapper/MusicMapper.ts diff --git a/src/FLAD/models/mapper/UserMapper.ts b/src/FLAD/models/mapper/UserMapper.ts new file mode 100644 index 0000000..0bfb37d --- /dev/null +++ b/src/FLAD/models/mapper/UserMapper.ts @@ -0,0 +1,7 @@ +import { User } from "../User"; + +export class UserMapper { + public static toModel(user: any): User { + return new User(user._id, user.idSpotify, user.name, user.email, new Date(user.createdAt), user.image); + } +} \ No newline at end of file diff --git a/src/FLAD/navigation/AuthNavigation.tsx b/src/FLAD/navigation/AuthNavigation.tsx index 1a4b1a1..0f7bf68 100644 --- a/src/FLAD/navigation/AuthNavigation.tsx +++ b/src/FLAD/navigation/AuthNavigation.tsx @@ -1,49 +1,49 @@ -import HomeNavigation from './HomeNavigation'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { SafeAreaProvider } from 'react-native-safe-area-context'; -import StartNavigation from './StartNavigation'; import { useDispatch, useSelector } from 'react-redux'; import { useEffect, useState } from 'react'; import * as SplashScreen from 'expo-splash-screen'; -import { ChangeMode, getRefreshToken } from '../redux/thunk/authThunk'; +import { getRefreshToken } from '../redux/thunk/authThunk'; +import { darkMode } from '../redux/thunk/userThunk'; +import HomeNavigation from './HomeNavigation'; +import StartNavigation from './StartNavigation'; export default function AuthNavigation() { //@ts-ignore - const tokenProcesed: boolean = useSelector(state => state.userReducer.loading); + const tokenProcessed: boolean = useSelector(state => state.userReducer.loading); //@ts-ignore const isLogin: boolean = useSelector(state => state.userReducer.isLogedIn); const [appIsReady, setAppIsReady] = useState(false); const dispatch = useDispatch(); + async function prepare() { //@ts-ignore await dispatch(getRefreshToken()) - if (tokenProcesed && appIsReady) { + } + + async function check() { + if (tokenProcessed && appIsReady) { await SplashScreen.hideAsync(); } } - async function ChangeDarkMode() { - try { - const currentValue = await AsyncStorage.getItem('dark'); - if (currentValue !== null) { - const newValue = JSON.stringify(JSON.parse(currentValue)); - //@ts-ignore - dispatch(ChangeMode(JSON.parse(newValue))) - } - } catch (error) { - console.log("An error occurred while updating the boolean value for the 'dark' key: ", error); + async function initDarkMode() { + const currentValue: string | null = await AsyncStorage.getItem('dark'); + if (currentValue) { + const newValue = JSON.stringify(JSON.parse(currentValue)); + //@ts-ignore + dispatch(darkMode(JSON.parse(newValue))) } } - useEffect(() => { - ChangeDarkMode(); - prepare(); - - }, [appIsReady, tokenProcesed]); + useEffect(() => { + check(); + }, [appIsReady, tokenProcessed]); - if (tokenProcesed == false) { - return null; - } + useEffect(() => { + prepare(); + initDarkMode(); + }, []); return ( diff --git a/src/FLAD/navigation/HomeNavigation.tsx b/src/FLAD/navigation/HomeNavigation.tsx index 849a5a1..c92640b 100644 --- a/src/FLAD/navigation/HomeNavigation.tsx +++ b/src/FLAD/navigation/HomeNavigation.tsx @@ -16,20 +16,20 @@ import { colorsLight } from '../constants/colorsLight'; import { getCurrentUserMusic, getSpotList } from '../redux/thunk/spotThunk'; import SpotifyService from '../services/spotify/spotify.service'; import * as SecureStore from 'expo-secure-store'; -import { MY_SECURE_AUTH_STATE_KEY } from '../screens/RegisterScreen'; import * as Location from 'expo-location'; import axios from 'axios'; import qs from 'qs'; +const MY_SECURE_AUTH_STATE_KEY = 'MySecureAuthStateKeySpotify'; + export default function HomeNavigation() { const [setErrorMsg] = useState(''); - const [location, setLocation] = useState(); -//@ts-ignore -const tokenSend: string = useSelector(state => state.userReducer.userFladToken); - //@ts-ignore - const currentMusic: Music = useSelector(state => state.appReducer.userCurrentMusic); + //@ts-ignore + const tokenSend: string = useSelector(state => state.userReducer.userFladToken); + //@ts-ignore + const currentMusic: Music = useSelector(state => state.appReducer.userCurrentMusic); - const dispatch = useDispatch(); + const dispatch = useDispatch(); const requestLocationPermission = async () => { const { status } = await Location.requestForegroundPermissionsAsync(); @@ -45,46 +45,46 @@ const tokenSend: string = useSelector(state => state.userReducer.userFladToken); const sendLocationUpdate = async () => { try { - let tmpKey: string = await SecureStore.getItemAsync(MY_SECURE_AUTH_STATE_KEY) ; + let tmpKey: string = await SecureStore.getItemAsync(MY_SECURE_AUTH_STATE_KEY); //@ts-ignore dispatch(getCurrentUserMusic(new SpotifyService(tmpKey))) let { status } = await Location.requestForegroundPermissionsAsync(); if (status == 'granted') { // should app is ready - const locationresp = await Location.getCurrentPositionAsync({}); - // send location to server - if(currentMusic){ - const body: Record = { - longitude: locationresp.coords.longitude, - latitude: locationresp.coords.latitude, - currentMusic: currentMusic.id - } - const resp = await axios({ - url: 'https://flad-api-production.up.railway.app/api/users/nextTo?' + qs.stringify(body), - method: 'GET', - headers: { - Authorization: `Bearer ${tokenSend}`, - }, - }); - const datat: Record = resp.data.listUser2; - //@ts-ignore - dispatch(getSpotList(datat, new SpotifyService(tmpKey))) + const locationresp = await Location.getCurrentPositionAsync({}); + // send location to server + if (currentMusic) { + const body: Record = { + longitude: locationresp.coords.longitude, + latitude: locationresp.coords.latitude, + currentMusic: currentMusic.id } - else{ - return; - } - - + const resp = await axios({ + url: 'https://flad-api-production.up.railway.app/api/users/nextTo?' + qs.stringify(body), + method: 'GET', + headers: { + Authorization: `Bearer ${tokenSend}`, + }, + }); + const datat: Record = resp.data.listUser2; + //@ts-ignore + dispatch(getSpotList(datat, new SpotifyService(tmpKey))) + } + else { + return; + } + + } else { //@ts-ignore - let {status} = Location.requestForegroundPermissionsAsync(); + let { status } = Location.requestForegroundPermissionsAsync(); if (status !== 'granted') { setErrorMsg('Permission to access location was denied'); return; } return; - + } } catch (error) { console.log(error); diff --git a/src/FLAD/package.json b/src/FLAD/package.json index abb1630..589354b 100644 --- a/src/FLAD/package.json +++ b/src/FLAD/package.json @@ -14,7 +14,7 @@ "@react-navigation/stack": "^6.3.12", "@reduxjs/toolkit": "^1.9.2", "@types/react-redux": "^7.1.25", - "axios": "^1.2.6", + "axios": "^1.6.0", "expo": "~47.0.12", "expo-auth-session": "~3.8.0", "expo-av": "~13.0.3", @@ -22,17 +22,14 @@ "expo-haptics": "~12.0.1", "expo-image-picker": "~14.0.2", "expo-linear-gradient": "~12.0.1", - "expo-linking": "~3.3.1", "expo-location": "~15.0.1", "expo-random": "~13.0.0", "expo-secure-store": "~12.0.0", "expo-splash-screen": "~0.17.5", - "expo-status-bar": "~1.4.2", - "expo-web-browser": "~12.0.0", "lottie-react-native": "5.1.4", "react": "18.1.0", "react-dom": "18.1.0", - "react-native": "0.70.14", + "react-native": "0.70.8", "react-native-gesture-handler": "~2.8.0", "react-native-gifted-chat": "^2.0.1", "react-native-modal": "^13.0.1", @@ -42,7 +39,6 @@ "react-native-shared-element": "0.8.4", "react-native-svg": "13.4.0", "react-native-vector-icons": "^9.2.0", - "react-native-web": "~0.18.9", "react-navigation-shared-element": "^3.1.3", "react-redux": "^8.0.5", "redux": "^4.2.1" diff --git a/src/FLAD/redux/actions/appActions.ts b/src/FLAD/redux/actions/appActions.ts index 2a19a45..8f4ee12 100644 --- a/src/FLAD/redux/actions/appActions.ts +++ b/src/FLAD/redux/actions/appActions.ts @@ -1,4 +1,4 @@ -import Music from "../../model/Music"; +import Music from "../../models/Music"; import { favoritesTypes } from "../types/favoritesTypes"; export const getFavoritesMusic = (music: Music[]) => { diff --git a/src/FLAD/redux/actions/spotActions.tsx b/src/FLAD/redux/actions/spotActions.tsx index 332abb4..0ed80df 100644 --- a/src/FLAD/redux/actions/spotActions.tsx +++ b/src/FLAD/redux/actions/spotActions.tsx @@ -1,5 +1,5 @@ -import Music from "../../model/Music"; -import { Spot } from "../../model/Spot"; +import Music from "../../models/Music"; +import { Spot } from "../../models/Spot"; import { spotifyTypes } from "../types/spotifyTypes"; import { spotTypes } from "../types/spotTypes"; diff --git a/src/FLAD/redux/actions/userActions.tsx b/src/FLAD/redux/actions/userActions.tsx index 8504dc9..d1bc54b 100644 --- a/src/FLAD/redux/actions/userActions.tsx +++ b/src/FLAD/redux/actions/userActions.tsx @@ -1,4 +1,4 @@ -import { User } from "../../model/User"; +import { User } from "../../models/User"; import { userTypes } from "../types/userTypes"; export interface LoginCredentials { @@ -10,22 +10,19 @@ export interface RegisterCredentials { email: string, password: string, name: string, - idFlad: string, - idSpotify: string + tokenSpotify: string } -export const setLoginState = (userJson: any) => { - const user = new User(userJson.data.idFlad, userJson.data.idSpotify, userJson.data.email, new Date(), userJson.data.name, userJson.data.image); +export const userLogin = (user: User) => { return { type: userTypes.LOGIN, payload: user }; } -export const restoreToken = (token: string) => { +export const restoreToken = () => { return { - type: userTypes.RESTORE_TOKEN, - payload: token + type: userTypes.RESTORE_TOKEN }; } @@ -56,7 +53,7 @@ export const setErrorLogin = (value: boolean) => { }; } -export const setErrorSignup = (value: boolean) => { +export const setErrorSignup = (value: string) => { return { type: userTypes.ERROR_SIGNUP, payload: value diff --git a/src/FLAD/redux/reducers/appReducer.tsx b/src/FLAD/redux/reducers/appReducer.tsx index 94f161e..f42066e 100644 --- a/src/FLAD/redux/reducers/appReducer.tsx +++ b/src/FLAD/redux/reducers/appReducer.tsx @@ -1,6 +1,4 @@ import { spotsData } from "../../data/data"; -import Music from "../../model/Music"; -import { Spot } from "../../model/Spot"; import { discoveriesTypes } from "../types/discoverieTypes"; import { favoritesTypes } from "../types/favoritesTypes"; import { spotifyTypes } from "../types/spotifyTypes"; diff --git a/src/FLAD/redux/reducers/userReducer.tsx b/src/FLAD/redux/reducers/userReducer.tsx index 9dd9ca8..e99e138 100644 --- a/src/FLAD/redux/reducers/userReducer.tsx +++ b/src/FLAD/redux/reducers/userReducer.tsx @@ -1,14 +1,14 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; -import { User } from "../../model/User"; +import { User } from "../../models/User"; import { userTypes } from "../types/userTypes"; const initialState = { user: User, - userFladToken: "", - userSpotifyToken: null, isLogedIn: false, + loading: false, failedLogin: false, failedSignup: false, + errorMessage: null, errorNetwork: false, dark: null } @@ -16,25 +16,27 @@ const initialState = { const userReducer = (state = initialState, action: any) => { switch (action.type) { case userTypes.RESTORE_TOKEN: - const resp = (action.payload == "" ? false : true) return { ...state, - userFladToken: action.payload, loading: true, - isLogedIn: resp, }; case userTypes.LOGIN: return { ...state, user: action.payload, - isLogedIn: true + isLogedIn: true, + failedLogin: false, + failedSignup: false, + errorNetwork: false }; case userTypes.SIGNUP: return { ...state, user: action.payload, isLogedIn: true, - dark: false + failedLogin: false, + failedSignup: false, + errorNetwork: false }; case userTypes.USER_LOGOUT: AsyncStorage.removeItem('dark'); @@ -52,7 +54,7 @@ const userReducer = (state = initialState, action: any) => { case userTypes.ERROR_LOGIN: return { ...state, failedLogin: action.payload } case userTypes.ERROR_SIGNUP: - return { ...state, failedSignup: action.payload } + return { ...state, failedSignup: true, errorMessage: action.payload } case userTypes.DARK_MODE: return { ...state, dark: action.payload } case userTypes.ERROR_NETWORK: @@ -61,5 +63,6 @@ const userReducer = (state = initialState, action: any) => { return state; } } -export default userReducer + +export default userReducer; \ No newline at end of file diff --git a/src/FLAD/redux/thunk/authThunk.tsx b/src/FLAD/redux/thunk/authThunk.tsx index 755ae0c..6f29987 100644 --- a/src/FLAD/redux/thunk/authThunk.tsx +++ b/src/FLAD/redux/thunk/authThunk.tsx @@ -1,52 +1,59 @@ import axios from "axios"; import configs from "../../constants/config"; -import { LoginCredentials, RegisterCredentials, restoreToken, setLoginState, userLogout, setDarkMode, userSignUp, setErrorLogin, setErrorSignup, setErrorNetwork } from "../actions/userActions"; +import { LoginCredentials, RegisterCredentials, restoreToken, userLogin, userLogout, userSignUp, setErrorLogin, setErrorSignup, setErrorNetwork } from "../actions/userActions"; import * as SecureStore from 'expo-secure-store'; -import { UserMapper } from "../../model/mapper/UserMapper"; +import { UserMapper } from "../../models/mapper/UserMapper"; const key = 'userToken'; +const keyRemember = 'rememberUser'; export const register = (resgisterCredential: RegisterCredentials) => { //@ts-ignore return async dispatch => { try { - const config = { headers: { 'Content-Type': 'application/json', }, } const resp = await axios.post( - configs.API_URL + '/users/register', + configs.API_URL + '/auth/register', resgisterCredential, config ) + const token = resp.data.token; + await SecureStore.setItemAsync(key, token); + await SecureStore.setItemAsync(keyRemember, 'true'); + const headers = { + 'Authorization': 'Bearer ' + token + }; + const user = await axios.get( + configs.API_URL + '/user', + { headers } + ) + dispatch(userSignUp(UserMapper.toModel(user.data.data))); - if (resp.data.token) { - const token = resp.data.token; - const headers = { - 'Authorization': 'Bearer ' + token - }; - const user = await axios.get( - configs.API_URL + 'api/users', - { headers } - ) - dispatch(userSignUp(UserMapper.toModel(user.data))); - } else { - dispatch(setErrorSignup(true)) - } - - } catch (error) { - if (axios.isAxiosError(error)) { - dispatch(setErrorNetwork(true)); - } else { - dispatch(setErrorLogin(true)); + } catch (error: any) { + console.error("Error : " + error.message); + switch (error.response.status) { + case 400: + dispatch(setErrorSignup("Email non valide !")); + break; + case 409: + dispatch(setErrorSignup("Email, Spotify ou nom déjà utilisé !")); + break; + case 500: + dispatch(setErrorSignup("Compte Spotify non autorisé !")); + break; + default: + dispatch(setErrorSignup("Erreur lors de l'inscription !")); + break; } } } } -export const login = (loginCredential: LoginCredentials) => { +export const login = (loginCredential: LoginCredentials, remember: boolean) => { //@ts-ignore return async dispatch => { try { @@ -55,37 +62,38 @@ export const login = (loginCredential: LoginCredentials) => { 'Content-Type': 'application/json', }, } - console.log(configs.API_URL + '/users/login') - console.log(loginCredential) - console.log(config) + const resp = await axios.post( - configs.API_URL + '/users/login', + configs.API_URL + '/auth/login', loginCredential, config ) - if (resp.data.token) { - const token = resp.data.token; - await SecureStore.setItemAsync(key, token); - const headers = { - 'Authorization': 'Bearer ' + token - }; - - const user = await axios.get( - configs.API_URL + '/users', - { headers } - ) - dispatch(setLoginState(user.data)); - } else { - console.log('Login Failed', 'Username or Password is incorrect'); - dispatch(setErrorLogin(true)); + const token = resp.data.token; + await SecureStore.setItemAsync(key, token); + if (remember) { + await SecureStore.setItemAsync(keyRemember, remember.toString()); } - } catch (error) { - if (axios.isAxiosError(error)) { - console.log("axios : " + error.message); - dispatch(setErrorNetwork(true)); - } else { - dispatch(setErrorLogin(true)); + + const headers = { + 'Authorization': 'Bearer ' + token + }; + + const user = await axios.get( + configs.API_URL + '/user', + { headers } + ) + dispatch(userLogin(UserMapper.toModel(user.data.data))); + + } catch (error: any) { + console.error("Error : " + error.message); + switch (error.response.status) { + case 400: + dispatch(setErrorLogin(true)); + break; + default: + dispatch(setErrorNetwork(true)); + break; } } } @@ -94,45 +102,60 @@ export const login = (loginCredential: LoginCredentials) => { export const getRefreshToken = () => { //@ts-ignore return async dispatch => { - try { - let userToken: string | null = await SecureStore.getItemAsync(key); - - if (userToken) { - dispatch(restoreToken(userToken)); + let remember: string | null = await SecureStore.getItemAsync(keyRemember); + let token: string | null = await SecureStore.getItemAsync(key); + if (token) { + if (remember) { + const headers = { + 'Authorization': 'Bearer ' + token + }; + try { + const user = await axios.get( + configs.API_URL + '/user', + { headers } + ) + await dispatch(userLogin(UserMapper.toModel(user.data.data))); + } catch (error: any) { + await SecureStore.deleteItemAsync(key); + dispatch(userLogout()); + } } else { - const empty = ""; - dispatch(restoreToken(empty)); + await SecureStore.deleteItemAsync(key); + dispatch(userLogout()); } - } catch (e) { - console.log('Error :', e); } - } -} - + dispatch(restoreToken()); -export const deleteToken = () => { - //@ts-ignore - return async dispatch => { - try { - await SecureStore.deleteItemAsync(key); - dispatch(userLogout()); - } catch (e) { - console.log('Error deleting token', e); - } } } -export const darkMode = (value: boolean) => { +export const deleteUser = () => { //@ts-ignore return async dispatch => { - dispatch(setDarkMode(value)); + let token: string | null = await SecureStore.getItemAsync(key); + if (token) { + const headers = { + 'Authorization': 'Bearer ' + token + }; + try { + await axios.delete( + configs.API_URL + '/user', + { headers } + ) + await SecureStore.deleteItemAsync(key); + dispatch(userLogout()); + } catch (error: any) { + console.error("Error deleting account : " + error.message); + } + } } } -export const imageUserCurrent = (value: any) => { +export const logout = () => { //@ts-ignore return async dispatch => { - //@ts-ignore - dispatch(setImageUserCurrent(value)); + await SecureStore.deleteItemAsync(key); + await SecureStore.deleteItemAsync(keyRemember); + dispatch(userLogout()); } } \ No newline at end of file diff --git a/src/FLAD/redux/thunk/socialThunk.tsx b/src/FLAD/redux/thunk/socialThunk.tsx index a03f1e0..fbb323c 100644 --- a/src/FLAD/redux/thunk/socialThunk.tsx +++ b/src/FLAD/redux/thunk/socialThunk.tsx @@ -1,5 +1,5 @@ import axios from "axios"; -import { Spot } from "../../model/Spot"; +import { Spot } from "../../models/Spot"; export const likeSpot = async (spot: Spot) => { return async (dispatch) => { diff --git a/src/FLAD/redux/thunk/spotThunk.tsx b/src/FLAD/redux/thunk/spotThunk.tsx index 28f6365..f576c33 100644 --- a/src/FLAD/redux/thunk/spotThunk.tsx +++ b/src/FLAD/redux/thunk/spotThunk.tsx @@ -1,6 +1,6 @@ import axios from "axios"; import * as SecureStore from 'expo-secure-store'; -import { Spot } from "../../model/Spot"; +import { Spot } from "../../models/Spot"; import SpotifyService from "../../services/spotify/spotify.service"; import { setSpotList, setUserCurrentMusic } from "../actions/spotActions"; const key = 'userToken'; diff --git a/src/FLAD/redux/thunk/userThunk.tsx b/src/FLAD/redux/thunk/userThunk.tsx new file mode 100644 index 0000000..7dbb7c4 --- /dev/null +++ b/src/FLAD/redux/thunk/userThunk.tsx @@ -0,0 +1,8 @@ +import { setDarkMode } from "../actions/userActions"; + +export const darkMode = (value: boolean) => { + //@ts-ignore + return async dispatch => { + dispatch(setDarkMode(value)); + } +} \ No newline at end of file diff --git a/src/FLAD/screens/DetailScreen.tsx b/src/FLAD/screens/DetailScreen.tsx index e49647a..40efbac 100644 --- a/src/FLAD/screens/DetailScreen.tsx +++ b/src/FLAD/screens/DetailScreen.tsx @@ -5,7 +5,7 @@ import Animated, { interpolate, SensorType, useAnimatedSensor, useAnimatedStyle, import { Audio } from 'expo-av'; import { useEffect, useState } from "react"; import normalize from '../components/Normalize'; -import Music from "../model/Music"; +import Music from "../models/Music"; import SpotifyService from "../services/spotify/spotify.service"; import { LinearGradient } from "expo-linear-gradient"; import FontAwesome from 'react-native-vector-icons/FontAwesome'; diff --git a/src/FLAD/screens/FavoriteScreen.tsx b/src/FLAD/screens/FavoriteScreen.tsx index 9c95834..6a4db9f 100644 --- a/src/FLAD/screens/FavoriteScreen.tsx +++ b/src/FLAD/screens/FavoriteScreen.tsx @@ -2,7 +2,8 @@ import React from 'react'; import { StyleSheet, Text, View, FlatList, TouchableHighlight, SafeAreaView } from 'react-native'; import CardMusic from '../components/CardMusicComponent'; import normalize from '../components/Normalize'; -import Music from '../model/Music' +import Music from '../models/Music'; +import { Svg, Path } from 'react-native-svg'; import FladyComponent from '../components/FladyComponent'; import { useNavigation } from "@react-navigation/native"; import { useSelector } from 'react-redux'; @@ -38,6 +39,12 @@ export default function FavoriteScreen() { marginTop: 10, marginLeft: 20, }, + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingRight: 20 + }, title: { fontSize: normalize(28), fontWeight: 'bold', @@ -78,7 +85,13 @@ export default function FavoriteScreen() { return ( - Favoris + + Favoris + + + + + Retrouvez ici vos musiques favorites state.userReducer.failedLogin); // @ts-ignore const networkError = useSelector(state => state.userReducer.errorNetwork); - const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); + const dispatch = useDispatch(); async function playSound() { const { sound } = await Audio.Sound.createAsync( @@ -34,35 +34,34 @@ export default function LoginScreen() { setSound(sound); await sound.playAsync(); } - const dispatch = useDispatch(); const submitForm = () => { const credentials: LoginCredentials = { email: username.toLowerCase(), - password: password.toLowerCase() + password: password }; //@ts-ignore - dispatch(login(credentials)) + dispatch(login(credentials, rememberMe)) playSound() } useEffect(() => { if (networkError) { - Alert.alert( - 'Erreur réseau', - 'Une erreur réseau s\'est produite. Veuillez vérifier votre connexion internet et réessayer.', - [ - { - text: 'OK', - onPress: () => { - dispatch(setErrorNetwork(false)); - }, - }, - ], - { cancelable: false } - ); + Alert.alert( + 'Erreur réseau', + 'Une erreur réseau s\'est produite. Veuillez vérifier votre connexion internet et réessayer.', + [ + { + text: 'OK', + onPress: () => { + dispatch(setErrorNetwork(false)); + }, + }, + ], + { cancelable: false } + ); } - }, [networkError, dispatch]); + }, [networkError]); const toggleRememberMe = () => { setRememberMe(!rememberMe); @@ -96,7 +95,11 @@ export default function LoginScreen() { - + + {rememberMe && ( + + )} + SE SOUVENIR DE MOI @@ -154,6 +157,10 @@ const styles = StyleSheet.create({ width: normalize(46), height: normalize(46), }, + checkBoxImage: { + width: normalize(14), + height: normalize(11), + }, iconUser: { position: 'absolute', width: 20, @@ -225,7 +232,12 @@ const styles = StyleSheet.create({ color: 'white' }, checkboxChecked: { - backgroundColor: 'white' + backgroundColor: '#5C1DC3', + borderColor: '#5C1DC3', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + alignSelf: 'center' }, inscriptionText: { flexDirection: 'row', diff --git a/src/FLAD/screens/ProfilScreen.tsx b/src/FLAD/screens/ProfilScreen.tsx index 4d94971..3a49b9a 100644 --- a/src/FLAD/screens/ProfilScreen.tsx +++ b/src/FLAD/screens/ProfilScreen.tsx @@ -1,16 +1,16 @@ -import React, { useEffect } from 'react'; -import { View, Text, StyleSheet, TouchableWithoutFeedback, Keyboard, ScrollView, Image } from 'react-native'; +import React from 'react'; +import { Alert, View, Text, StyleSheet, TouchableWithoutFeedback, Keyboard, ScrollView, Image } from 'react-native'; import { TextInput, TouchableOpacity } from 'react-native-gesture-handler'; import { Svg, Path } from 'react-native-svg'; import Modal from "react-native-modal"; import { useNavigation } from "@react-navigation/native"; -import { useSelector } from 'react-redux'; - +import { useDispatch, useSelector } from 'react-redux'; import normalize from '../components/Normalize'; import * as ImagePicker from 'expo-image-picker'; import { SafeAreaView } from 'react-native-safe-area-context'; import { colorsDark } from '../constants/colorsDark'; import { colorsLight } from '../constants/colorsLight'; +import { deleteUser } from '../redux/thunk/authThunk'; // @ts-ignore const DismissKeyboard = ({ children }) => ( @@ -23,17 +23,33 @@ export default function ProfilScreen() { // @ts-ignore const isDark = useSelector(state => state.userReducer.dark); // @ts-ignore - const UserCurrent = useSelector(state => state.userReducer.user); + const userCurrent = useSelector(state => state.userReducer.user); const style = isDark ? colorsDark : colorsLight; const navigation = useNavigation(); const [isModalVisible, setIsModalVisible] = React.useState(false); - - useEffect(() => { - console.log(UserCurrent.image); - }); + const dispatch = useDispatch(); const handleModal = () => setIsModalVisible(() => !isModalVisible); + const deleteAccount = () => { + Alert.alert( + 'Confirmation', + 'Êtes-vous sûr de vouloir supprimer votre compte ?', + [ + { + text: 'Annuler', + style: 'cancel' + }, + { + text: 'Oui', + //@ts-ignore + onPress: () => dispatch(deleteUser()), + style: 'destructive' + }, + ], + { cancelable: false } + ); + }; const pickImage = async () => { await ImagePicker.launchImageLibraryAsync({ @@ -56,8 +72,8 @@ export default function ProfilScreen() { backgroundColor: style.body, }, buttonSetting: { - width: normalize(17), - height: normalize(17), + width: normalize(11), + height: normalize(18), marginRight: 5 }, modalContent: { @@ -257,7 +273,7 @@ export default function ProfilScreen() { // @ts-ignore onPress={() => navigation.navigate('Setting')}> - + Exit @@ -265,18 +281,18 @@ export default function ProfilScreen() { Profil - + Identifiant - + Mail - + @@ -295,16 +311,11 @@ export default function ProfilScreen() { - - - - - - - + + - console.log("Tkt t deconnecter")}> + deleteAccount()}> Supprimer le compte diff --git a/src/FLAD/screens/RegisterScreen.tsx b/src/FLAD/screens/RegisterScreen.tsx index 74e85db..b3bf15e 100644 --- a/src/FLAD/screens/RegisterScreen.tsx +++ b/src/FLAD/screens/RegisterScreen.tsx @@ -1,16 +1,15 @@ -import React, { useEffect, useState } from 'react'; -import { View, Image, StyleSheet, Text, ImageBackground, TextInput, TouchableWithoutFeedback, Keyboard, TouchableOpacity, Platform } from 'react-native'; +import React, { useState } from 'react'; +import { Alert, View, Image, StyleSheet, Text, ImageBackground, TextInput, TouchableWithoutFeedback, Keyboard, TouchableOpacity, Platform } from 'react-native'; import { useNavigation } from "@react-navigation/native"; import normalize from '../components/Normalize'; -import * as SecureStore from 'expo-secure-store'; import * as AuthSession from 'expo-auth-session'; import { register } from '../redux/thunk/authThunk'; import { useDispatch, useSelector } from 'react-redux'; import { Audio } from 'expo-av'; import { RegisterCredentials } from '../redux/actions/userActions'; -import * as WebBrowser from 'expo-web-browser'; import { setSpotList } from '../redux/actions/spotActions'; import { spotsData } from '../data/data'; +import configs from '../constants/config'; // @ts-ignore const DismissKeyboard = ({ children }) => ( @@ -19,21 +18,18 @@ const DismissKeyboard = ({ children }) => ( ) -export const MY_SECURE_AUTH_STATE_KEY = 'MySecureAuthStateKeySpotify'; -export const MY_SECURE_AUTH_STATE_KEY_REFRESH = 'MySecureAuthStateKeySpotifyREFRESH'; - -WebBrowser.maybeCompleteAuthSession(); - -// save the spotifyToken -async function save(key: string, value: string) { - await SecureStore.setItemAsync(key, value); -} export default function RegisterScreen() { const [sound, setSound] = useState(); const navigation = useNavigation(); - const [spotifyToken, setSpotifyToken] = useState(''); + const [spotifyToken, setSpotifyToken] = useState(); + const [username, setUsername] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const dispatch = useDispatch(); // @ts-ignore const failedSignup = useSelector(state => state.userReducer.failedSignup); + // @ts-ignore + const errorMessage = useSelector(state => state.userReducer.errorMessage); async function playSound() { const { sound } = await Audio.Sound.createAsync( @@ -42,24 +38,42 @@ export default function RegisterScreen() { setSound(sound); await sound.playAsync(); } - const [username, setUsername] = useState(''); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - - - const dispatch = useDispatch(); function addMockSpots() { dispatch(setSpotList(spotsData)) } const submitForm = () => { + const isUsernameValid = /^[a-zA-Z0-9_]+$/.test(username); + const isEmailValid = /^[a-zA-Z0-9_]+@[a-zA-Z0-9_]+\.[^\s@]+$/.test(email); + + if (username == "" || username == null) { + Alert.alert("Erreur inscription", "Le nom d'utilisateur ne peut pas être vide."); + return; + } + if (!isUsernameValid) { + Alert.alert("Erreur inscription", "Le nom d'utilisateur ne peut pas posséder de caractères spéciaux."); + return; + } + if (!isEmailValid) { + Alert.alert("Erreur inscription", "L'adresse e-mail n\'est pas valide."); + return; + } + if (password.length < 6) { + Alert.alert("Erreur inscription", "Le mot de passe doit avoir au moins 6 caractères"); + return; + } + + if (spotifyToken == null || spotifyToken == "") { + Alert.alert("Erreur inscription", "Pour vous inscrire, veuillez vous connecter à Spotify."); + return; + } + const credentials: RegisterCredentials = { email: email, password: password, - idSpotify: spotifyToken, - name: username, - idFlad: generateRandomString() + tokenSpotify: spotifyToken, + name: username }; //@ts-ignore @@ -68,39 +82,25 @@ export default function RegisterScreen() { playSound() } - function generateRandomString(): string { - const alphabet = 'abcdefghijklmnopqrstuvwxyz'; - let randomString = ''; - - for (let i = 0; i < 8; i++) { - const randomIndex = Math.floor(Math.random() * alphabet.length); - const randomChar = alphabet[randomIndex]; - randomString += randomChar; - } - - return randomString; - } - - const getTokens2 = async () => { + const getSpotifyToken = async () => { try { const redirectUri = AuthSession.makeRedirectUri(); - const result = await AuthSession.startAsync({ - authUrl: 'https://flad-api-production.up.railway.app/api/spotify/exchange?' + '&redirectUrl=' + - encodeURIComponent(redirectUri) + const result: any = await AuthSession.startAsync({ + authUrl: configs.API_URL + '/spotify/exchange?' + 'redirectUrl=' + + encodeURIComponent(redirectUri) }) const { access_token: access_token, refresh_token: refresh_token, } = result.params - save(MY_SECURE_AUTH_STATE_KEY, access_token); - setSpotifyToken(access_token) - save(MY_SECURE_AUTH_STATE_KEY_REFRESH, refresh_token); - } catch (err) { - console.error(err); + setSpotifyToken(refresh_token) + } catch (error) { + Alert.alert("Erreur inscription", "La connexion à Spotify à échouer."); + return; } } - + return ( @@ -111,7 +111,7 @@ export default function RegisterScreen() { S'INSCRIRE {failedSignup && ( - Email ou mot de passe incorrect! + {errorMessage} )} { - await getTokens2(); + await getSpotifyToken(); }} style={[styles.buttonSpotify, styles.shadow]}> Lier compte - + {spotifyToken == null ? ( + + ) : + + } - submitForm()}> diff --git a/src/FLAD/screens/SettingScreen.tsx b/src/FLAD/screens/SettingScreen.tsx index 47197eb..1b1d373 100644 --- a/src/FLAD/screens/SettingScreen.tsx +++ b/src/FLAD/screens/SettingScreen.tsx @@ -7,9 +7,11 @@ import { useDispatch, useSelector } from 'react-redux'; import normalize from '../components/Normalize'; import { ScrollView, Switch, TextInput } from 'react-native-gesture-handler'; import CardMusic from '../components/CardMusicComponent'; -import { ChangeMode, DeleteToken } from '../redux/thunk/authThunk'; +import { logout } from '../redux/thunk/authThunk'; +import { darkMode } from '../redux/thunk/userThunk'; import { colorsDark } from '../constants/colorsDark'; import { colorsLight } from '../constants/colorsLight'; +import { User } from '../models/User'; // @ts-ignore const DismissKeyboard = ({ children }) => ( @@ -30,44 +32,36 @@ export default function SettingScreen() { // @ts-ignore const currentMusic = useSelector(state => state.appReducer.userCurrentMusic); + // @ts-ignore + const currentUser: User = useSelector(state => state.userReducer.user); + // @ts-ignore const isDark = useSelector(state => state.userReducer.dark); const style = isDark ? colorsDark : colorsLight; async function ChangeDarkMode() { - try { - const currentValue = await AsyncStorage.getItem('dark'); - if (currentValue !== null) { - const newValue = JSON.stringify(!JSON.parse(currentValue)); - await AsyncStorage.setItem('dark', newValue); - // @ts-ignore - dispatch(ChangeMode(JSON.parse(newValue))) - } - } catch (error) { - console.log(`Une erreur s'est produite lors de la mise à jour de la valeur booléenne pour la clé 'dark': `, error); - } + const newValue = !isDark; + await AsyncStorage.setItem('dark', newValue.toString()); + // @ts-ignore + dispatch(darkMode(JSON.parse(newValue))) } - //Notification const [isCheckedNotif, setIsCheckedNotif] = useState(false); const toggleNotif = () => setIsCheckedNotif(value => !value); - //Deconnection const Deconnection = () => { //@ts-ignore - dispatch(DeleteToken()) + dispatch(logout()) } - //Localisation + const [isCheckedLocalisation, setIsCheckedLocalisation] = useState(false); const toggleLocalisation = () => setIsCheckedLocalisation(value => !value); - - //Style const styles = StyleSheet.create({ mainSafeArea: { flex: 1, @@ -110,6 +104,7 @@ export default function SettingScreen() { imageProfil: { marginLeft: 15, marginRight: 7, + borderRadius: 8, width: 50, height: 50 }, @@ -129,8 +124,8 @@ export default function SettingScreen() { justifyContent: 'center', }, buttonSetting: { - width: normalize(17), - height: normalize(17), + width: normalize(11), + height: normalize(18), marginRight: 22 }, body: { @@ -260,7 +255,7 @@ export default function SettingScreen() { - + @@ -271,9 +266,9 @@ export default function SettingScreen() { onPress={() => navigation.navigate('Profil')} > - + - Emre KARTAL + {currentUser.name} id. Spotify, mail et mot de passe @@ -283,10 +278,9 @@ export default function SettingScreen() { - - - - + + + @@ -296,7 +290,7 @@ export default function SettingScreen() { - + @@ -309,7 +303,7 @@ export default function SettingScreen() { - + @@ -335,14 +329,14 @@ export default function SettingScreen() { - + ) : <>} - +