Improved authentication on the API and CLIENT side with error management
continuous-integration/drone/push Build is passing Details

pull/19/head
Emre KARTAL 1 year ago
parent 9dc8a5be16
commit 0ce68c7c26

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 KiB

After

Width:  |  Height:  |  Size: 368 KiB

@ -1,3 +1,4 @@
import IController from './interfaces/IController'; import IController from './interfaces/IController';
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import HttpException from '../exception/HttpException'; import HttpException from '../exception/HttpException';
@ -50,7 +51,6 @@ class SpotifyController implements IController {
res: Response, res: Response,
next: NextFunction next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
try {
const params = req.query.refresh_token; const params = req.query.refresh_token;
if (!req.query.refresh_token) { if (!req.query.refresh_token) {
return res.json({ return res.json({
@ -80,11 +80,10 @@ class SpotifyController implements IController {
"expires_in": session.data.expires_in "expires_in": session.data.expires_in
}); });
} }
})
.catch(error => {
res.status(400).send("Cannot get a new refresh token");
}); });
} catch (error) {
next(new HttpException(400, 'Cannot get a new refresh token'));
}
} }
private getAccessToken = async ( private getAccessToken = async (
@ -123,7 +122,6 @@ class SpotifyController implements IController {
})); }));
} }
} catch (error) { } catch (error) {
console.log(error);
next(new HttpException(400, 'Error connection: ' + error.message)); next(new HttpException(400, 'Error connection: ' + error.message));
} }
}; };

File diff suppressed because one or more lines are too long

@ -8,6 +8,10 @@ const userSchema = new Schema({
required: true, required: true,
unique: true unique: true
}, },
tokenSpotify: {
type: String,
required: true
},
name: { name: {
type: String, type: String,
required: true, required: true,
@ -23,6 +27,11 @@ const userSchema = new Schema({
type: String, type: String,
required: true required: true
} }
,
image: {
type: String,
required: true
}
}, },
{ timestamps: true } { timestamps: true }
); );

@ -1,10 +1,11 @@
import Joi from 'joi'; import Joi from 'joi';
const register = Joi.object({ 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(), email: Joi.string().email().required(),
password: Joi.string().min(6).required(), password: Joi.string().min(6).required(),
idSpotify: Joi.string(), tokenSpotify: Joi.string().required()
}); });
const login = Joi.object({ const login = Joi.object({

@ -1,21 +1,27 @@
import LocationSchema from "../database/LocationSchema";
import UserSchema from "../database/UserSchema"; import UserSchema from "../database/UserSchema";
import token from "./TokenService"; import token from "./TokenService";
class UserService { class UserService {
private user = UserSchema; private user = UserSchema;
private location = LocationSchema;
public async register( public async register(
name: string, name: string,
email: string, email: string,
password: string, password: string,
idSpotify: string idSpotify: string,
tokenSpotify: string,
image: string
): Promise<string | Error> { ): Promise<string | Error> {
try { try {
const user = await this.user.create({ const user = await this.user.create({
name, name,
email, email,
password, password,
idSpotify tokenSpotify,
idSpotify,
image
}); });
return token.createToken(user); return token.createToken(user);
} catch (error: any) { } catch (error: any) {
@ -37,6 +43,18 @@ class UserService {
throw new Error('Wrong credentials given'); throw new Error('Wrong credentials given');
} }
} }
public async delete(
id: string
): Promise<string | Error> {
try {
await this.user.findByIdAndRemove(id);
await this.location.findByIdAndRemove(id);
return;
} catch (error: any) {
throw new Error(error.message);
}
}
} }
export default UserService; export default UserService;

@ -11,4 +11,3 @@ export default function App() {
</Provider> </Provider>
); );
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 677 B

After

Width:  |  Height:  |  Size: 934 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 B

After

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 MiB

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 KiB

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 760 KiB

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 756 KiB

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 MiB

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 778 KiB

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -5,7 +5,7 @@ import Animated, {
ZoomOut, ZoomOut,
} from "react-native-reanimated"; } from "react-native-reanimated";
import { Feather as Icon } from "@expo/vector-icons"; import { Feather as Icon } from "@expo/vector-icons";
import Music from "../model/Music"; import Music from "../models/Music";
import { useState } from "react"; import { useState } from "react";
const { width } = Dimensions.get("window"); const { width } = Dimensions.get("window");

@ -1,6 +1,6 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { View, Text, Image, Pressable, Linking, Alert } from 'react-native' import { View, Text, Image, Pressable, Linking, Alert } from 'react-native'
import Artist from '../model/Artist'; import Artist from '../models/Artist';
interface ArtistChipProps { interface ArtistChipProps {
backgroundColor: string; backgroundColor: string;

@ -133,7 +133,6 @@ const Card = ({ image, onSwipe }: CardProps) => {
return ( return (
<View> <View>
<PanGestureHandler onGestureEvent={onGestureEvent}> <PanGestureHandler onGestureEvent={onGestureEvent}>
<Animated.View style={[styleCardsNew, styles.container]}> <Animated.View style={[styleCardsNew, styles.container]}>
<Animated.View style={[styles.image, { <Animated.View style={[styles.image, {
backgroundColor: 'black', elevation: 100, backgroundColor: 'black', elevation: 100,

@ -10,7 +10,7 @@ interface CircularProps {
} }
const PI = Math.PI; const PI = Math.PI;
const FladInput = ({ background, foreground, progress }: CircularProps) => { export default function FladInput({ background, foreground, progress }: CircularProps) {
const theta = multiply(progress, 2 * PI); const theta = multiply(progress, 2 * PI);
const opacity = lessThan(theta, PI); const opacity = lessThan(theta, PI);
@ -28,8 +28,7 @@ const FladInput = ({ background, foreground, progress }: CircularProps) => {
<HalfCirlce backgroundColor={background} /> <HalfCirlce backgroundColor={background} />
</Animated.View> </Animated.View>
</View> </View>
</> </>
); );
}; };
export default FladInput;

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { StyleSheet, Text, View, Image } from 'react-native'; import { StyleSheet, Text, View, Image } from 'react-native';
import { color } from 'react-native-reanimated';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { colorsDark } from '../constants/colorsDark'; import { colorsDark } from '../constants/colorsDark';
import { colorsLight } from '../constants/colorsLight'; import { colorsLight } from '../constants/colorsLight';

@ -0,0 +1,20 @@
import { View } from 'react-native'
interface HalfCirlceProps {
backgroundColor: string;
}
export default function HalfCirlce({ backgroundColor }: HalfCirlceProps) {
return (
<View style={{
width: RADIUS * 2,
height: RADIUS * 2,
overflow: "hidden",
}}>
<View style={{ backgroundColor: backgroundColor, width: RADIUS * 2, height: RADIUS * 2, borderRadius: RADIUS, }}>
</View>
</View>
);
};

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { View, StyleSheet, Animated, useWindowDimensions } from 'react-native'; import { View, StyleSheet, Animated, useWindowDimensions } from 'react-native';
import normalize from './Normalize'; import normalize from './Normalize';
// @ts-ignore // @ts-ignore

@ -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<RiveRef>(null);
const handlePlay = () => { riveRef.current?.play() };
return (
<Pressable onPress={handlePlay}>
<Image source={Icons.discovery} style={[styles.image]} />
</Pressable>
);
};
const styles = StyleSheet.create({
button: {
justifyContent: 'center',
alignItems: 'center',
},
image: {
borderRadius: 24,
resizeMode: 'cover',
width: 65,
height: 65,
backgroundColor: 'black',
}
})

@ -1,5 +1,5 @@
import Music from "../model/Music"; import Music from "../models/Music";
import { Spot } from "../model/Spot"; import { Spot } from "../models/Spot";
export const spotsData: Spot[] = [ export const spotsData: Spot[] = [
new Spot("1", new Music("6KNw3UKRp3QRsO7Cf4ASVE", new Spot("1", new Music("6KNw3UKRp3QRsO7Cf4ASVE",

@ -8,7 +8,7 @@ export default [
{ {
id: '2', id: '2',
title: 'Tous les jours de nouvelles musiques qui peuvent vous plaire', 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') image: require('../assets/images/board_2.png')
}, },
{ {

@ -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);
}
}

@ -1,33 +1,33 @@
export class User { export class User {
private _idFlad: string; private _id: string;
private _idSpotify: string; private _idSpotify: string;
private _email: string;
private _createdAt: Date;
private _name: string; private _name: string;
private _email: string;
private _creationDate: Date;
public image: string; public image: string;
constructor(idFlad: string, idSpotify: string, email: string, createdAt: Date, name: string, image: string) { constructor(id: string, idSpotify: string, name: string, email: string, creationDate: Date, image: string) {
this._name = name; this._id = id;
this._idFlad = idFlad;
this._idSpotify = idSpotify; this._idSpotify = idSpotify;
this._createdAt = createdAt; this._name = name;
this._email = email; this._email = email;
this._creationDate = creationDate;
this.image = image; this.image = image;
} }
get idFlad(): string { get id(): string {
return this._idFlad; return this._id;
} }
get idSpotify(): string { get idSpotify(): string {
return this._idSpotify; return this._idSpotify;
} }
get name(): string {
return this._name;
}
get email(): string { get email(): string {
return this._email; return this._email;
} }
get createAt(): Date { get creationDate(): Date {
return this._createdAt; return this._creationDate;
}
get name(): string {
return this._name;
} }
} }

@ -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);
}
}

@ -1,49 +1,49 @@
import HomeNavigation from './HomeNavigation';
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import { SafeAreaProvider } from 'react-native-safe-area-context'; import { SafeAreaProvider } from 'react-native-safe-area-context';
import StartNavigation from './StartNavigation';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import * as SplashScreen from 'expo-splash-screen'; 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() { export default function AuthNavigation() {
//@ts-ignore //@ts-ignore
const tokenProcesed: boolean = useSelector(state => state.userReducer.loading); const tokenProcessed: boolean = useSelector(state => state.userReducer.loading);
//@ts-ignore //@ts-ignore
const isLogin: boolean = useSelector(state => state.userReducer.isLogedIn); const isLogin: boolean = useSelector(state => state.userReducer.isLogedIn);
const [appIsReady, setAppIsReady] = useState(false); const [appIsReady, setAppIsReady] = useState(false);
const dispatch = useDispatch(); const dispatch = useDispatch();
async function prepare() { async function prepare() {
//@ts-ignore //@ts-ignore
await dispatch(getRefreshToken()) await dispatch(getRefreshToken())
if (tokenProcesed && appIsReady) { }
async function check() {
if (tokenProcessed && appIsReady) {
await SplashScreen.hideAsync(); await SplashScreen.hideAsync();
} }
} }
async function ChangeDarkMode() { async function initDarkMode() {
try { const currentValue: string | null = await AsyncStorage.getItem('dark');
const currentValue = await AsyncStorage.getItem('dark'); if (currentValue) {
if (currentValue !== null) {
const newValue = JSON.stringify(JSON.parse(currentValue)); const newValue = JSON.stringify(JSON.parse(currentValue));
//@ts-ignore //@ts-ignore
dispatch(ChangeMode(JSON.parse(newValue))) dispatch(darkMode(JSON.parse(newValue)))
}
} catch (error) {
console.log("An error occurred while updating the boolean value for the 'dark' key: ", error);
} }
} }
useEffect(() => {
ChangeDarkMode();
prepare();
}, [appIsReady, tokenProcesed]);
useEffect(() => {
check();
}, [appIsReady, tokenProcessed]);
if (tokenProcesed == false) { useEffect(() => {
return null; prepare();
} initDarkMode();
}, []);
return ( return (

@ -16,14 +16,14 @@ import { colorsLight } from '../constants/colorsLight';
import { getCurrentUserMusic, getSpotList } from '../redux/thunk/spotThunk'; import { getCurrentUserMusic, getSpotList } from '../redux/thunk/spotThunk';
import SpotifyService from '../services/spotify/spotify.service'; import SpotifyService from '../services/spotify/spotify.service';
import * as SecureStore from 'expo-secure-store'; import * as SecureStore from 'expo-secure-store';
import { MY_SECURE_AUTH_STATE_KEY } from '../screens/RegisterScreen';
import * as Location from 'expo-location'; import * as Location from 'expo-location';
import axios from 'axios'; import axios from 'axios';
import qs from 'qs'; import qs from 'qs';
const MY_SECURE_AUTH_STATE_KEY = 'MySecureAuthStateKeySpotify';
export default function HomeNavigation() { export default function HomeNavigation() {
const [setErrorMsg] = useState(''); const [setErrorMsg] = useState('');
const [location, setLocation] = useState<Location.LocationObject>();
//@ts-ignore //@ts-ignore
const tokenSend: string = useSelector(state => state.userReducer.userFladToken); const tokenSend: string = useSelector(state => state.userReducer.userFladToken);
//@ts-ignore //@ts-ignore

@ -14,7 +14,7 @@
"@react-navigation/stack": "^6.3.12", "@react-navigation/stack": "^6.3.12",
"@reduxjs/toolkit": "^1.9.2", "@reduxjs/toolkit": "^1.9.2",
"@types/react-redux": "^7.1.25", "@types/react-redux": "^7.1.25",
"axios": "^1.2.6", "axios": "^1.6.0",
"expo": "~47.0.12", "expo": "~47.0.12",
"expo-auth-session": "~3.8.0", "expo-auth-session": "~3.8.0",
"expo-av": "~13.0.3", "expo-av": "~13.0.3",
@ -22,17 +22,14 @@
"expo-haptics": "~12.0.1", "expo-haptics": "~12.0.1",
"expo-image-picker": "~14.0.2", "expo-image-picker": "~14.0.2",
"expo-linear-gradient": "~12.0.1", "expo-linear-gradient": "~12.0.1",
"expo-linking": "~3.3.1",
"expo-location": "~15.0.1", "expo-location": "~15.0.1",
"expo-random": "~13.0.0", "expo-random": "~13.0.0",
"expo-secure-store": "~12.0.0", "expo-secure-store": "~12.0.0",
"expo-splash-screen": "~0.17.5", "expo-splash-screen": "~0.17.5",
"expo-status-bar": "~1.4.2",
"expo-web-browser": "~12.0.0",
"lottie-react-native": "5.1.4", "lottie-react-native": "5.1.4",
"react": "18.1.0", "react": "18.1.0",
"react-dom": "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-gesture-handler": "~2.8.0",
"react-native-gifted-chat": "^2.0.1", "react-native-gifted-chat": "^2.0.1",
"react-native-modal": "^13.0.1", "react-native-modal": "^13.0.1",
@ -42,7 +39,6 @@
"react-native-shared-element": "0.8.4", "react-native-shared-element": "0.8.4",
"react-native-svg": "13.4.0", "react-native-svg": "13.4.0",
"react-native-vector-icons": "^9.2.0", "react-native-vector-icons": "^9.2.0",
"react-native-web": "~0.18.9",
"react-navigation-shared-element": "^3.1.3", "react-navigation-shared-element": "^3.1.3",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"redux": "^4.2.1" "redux": "^4.2.1"

@ -1,4 +1,4 @@
import Music from "../../model/Music"; import Music from "../../models/Music";
import { favoritesTypes } from "../types/favoritesTypes"; import { favoritesTypes } from "../types/favoritesTypes";
export const getFavoritesMusic = (music: Music[]) => { export const getFavoritesMusic = (music: Music[]) => {

@ -1,5 +1,5 @@
import Music from "../../model/Music"; import Music from "../../models/Music";
import { Spot } from "../../model/Spot"; import { Spot } from "../../models/Spot";
import { spotifyTypes } from "../types/spotifyTypes"; import { spotifyTypes } from "../types/spotifyTypes";
import { spotTypes } from "../types/spotTypes"; import { spotTypes } from "../types/spotTypes";

@ -1,4 +1,4 @@
import { User } from "../../model/User"; import { User } from "../../models/User";
import { userTypes } from "../types/userTypes"; import { userTypes } from "../types/userTypes";
export interface LoginCredentials { export interface LoginCredentials {
@ -10,22 +10,19 @@ export interface RegisterCredentials {
email: string, email: string,
password: string, password: string,
name: string, name: string,
idFlad: string, tokenSpotify: string
idSpotify: string
} }
export const setLoginState = (userJson: any) => { export const userLogin = (user: User) => {
const user = new User(userJson.data.idFlad, userJson.data.idSpotify, userJson.data.email, new Date(), userJson.data.name, userJson.data.image);
return { return {
type: userTypes.LOGIN, type: userTypes.LOGIN,
payload: user payload: user
}; };
} }
export const restoreToken = (token: string) => { export const restoreToken = () => {
return { return {
type: userTypes.RESTORE_TOKEN, type: userTypes.RESTORE_TOKEN
payload: token
}; };
} }
@ -56,7 +53,7 @@ export const setErrorLogin = (value: boolean) => {
}; };
} }
export const setErrorSignup = (value: boolean) => { export const setErrorSignup = (value: string) => {
return { return {
type: userTypes.ERROR_SIGNUP, type: userTypes.ERROR_SIGNUP,
payload: value payload: value

@ -1,6 +1,4 @@
import { spotsData } from "../../data/data"; import { spotsData } from "../../data/data";
import Music from "../../model/Music";
import { Spot } from "../../model/Spot";
import { discoveriesTypes } from "../types/discoverieTypes"; import { discoveriesTypes } from "../types/discoverieTypes";
import { favoritesTypes } from "../types/favoritesTypes"; import { favoritesTypes } from "../types/favoritesTypes";
import { spotifyTypes } from "../types/spotifyTypes"; import { spotifyTypes } from "../types/spotifyTypes";

@ -1,14 +1,14 @@
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import { User } from "../../model/User"; import { User } from "../../models/User";
import { userTypes } from "../types/userTypes"; import { userTypes } from "../types/userTypes";
const initialState = { const initialState = {
user: User, user: User,
userFladToken: "",
userSpotifyToken: null,
isLogedIn: false, isLogedIn: false,
loading: false,
failedLogin: false, failedLogin: false,
failedSignup: false, failedSignup: false,
errorMessage: null,
errorNetwork: false, errorNetwork: false,
dark: null dark: null
} }
@ -16,25 +16,27 @@ const initialState = {
const userReducer = (state = initialState, action: any) => { const userReducer = (state = initialState, action: any) => {
switch (action.type) { switch (action.type) {
case userTypes.RESTORE_TOKEN: case userTypes.RESTORE_TOKEN:
const resp = (action.payload == "" ? false : true)
return { return {
...state, ...state,
userFladToken: action.payload,
loading: true, loading: true,
isLogedIn: resp,
}; };
case userTypes.LOGIN: case userTypes.LOGIN:
return { return {
...state, ...state,
user: action.payload, user: action.payload,
isLogedIn: true isLogedIn: true,
failedLogin: false,
failedSignup: false,
errorNetwork: false
}; };
case userTypes.SIGNUP: case userTypes.SIGNUP:
return { return {
...state, ...state,
user: action.payload, user: action.payload,
isLogedIn: true, isLogedIn: true,
dark: false failedLogin: false,
failedSignup: false,
errorNetwork: false
}; };
case userTypes.USER_LOGOUT: case userTypes.USER_LOGOUT:
AsyncStorage.removeItem('dark'); AsyncStorage.removeItem('dark');
@ -52,7 +54,7 @@ const userReducer = (state = initialState, action: any) => {
case userTypes.ERROR_LOGIN: case userTypes.ERROR_LOGIN:
return { ...state, failedLogin: action.payload } return { ...state, failedLogin: action.payload }
case userTypes.ERROR_SIGNUP: case userTypes.ERROR_SIGNUP:
return { ...state, failedSignup: action.payload } return { ...state, failedSignup: true, errorMessage: action.payload }
case userTypes.DARK_MODE: case userTypes.DARK_MODE:
return { ...state, dark: action.payload } return { ...state, dark: action.payload }
case userTypes.ERROR_NETWORK: case userTypes.ERROR_NETWORK:
@ -61,5 +63,6 @@ const userReducer = (state = initialState, action: any) => {
return state; return state;
} }
} }
export default userReducer
export default userReducer;

@ -1,52 +1,59 @@
import axios from "axios"; import axios from "axios";
import configs from "../../constants/config"; 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 * as SecureStore from 'expo-secure-store';
import { UserMapper } from "../../model/mapper/UserMapper"; import { UserMapper } from "../../models/mapper/UserMapper";
const key = 'userToken'; const key = 'userToken';
const keyRemember = 'rememberUser';
export const register = (resgisterCredential: RegisterCredentials) => { export const register = (resgisterCredential: RegisterCredentials) => {
//@ts-ignore //@ts-ignore
return async dispatch => { return async dispatch => {
try { try {
const config = { const config = {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
} }
const resp = await axios.post( const resp = await axios.post(
configs.API_URL + '/users/register', configs.API_URL + '/auth/register',
resgisterCredential, resgisterCredential,
config config
) )
if (resp.data.token) {
const token = resp.data.token; const token = resp.data.token;
await SecureStore.setItemAsync(key, token);
await SecureStore.setItemAsync(keyRemember, 'true');
const headers = { const headers = {
'Authorization': 'Bearer ' + token 'Authorization': 'Bearer ' + token
}; };
const user = await axios.get( const user = await axios.get(
configs.API_URL + 'api/users', configs.API_URL + '/user',
{ headers } { headers }
) )
dispatch(userSignUp(UserMapper.toModel(user.data))); dispatch(userSignUp(UserMapper.toModel(user.data.data)));
} else {
dispatch(setErrorSignup(true))
}
} catch (error) { } catch (error: any) {
if (axios.isAxiosError(error)) { console.error("Error : " + error.message);
dispatch(setErrorNetwork(true)); switch (error.response.status) {
} else { case 400:
dispatch(setErrorLogin(true)); 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 //@ts-ignore
return async dispatch => { return async dispatch => {
try { try {
@ -55,37 +62,38 @@ export const login = (loginCredential: LoginCredentials) => {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
} }
console.log(configs.API_URL + '/users/login')
console.log(loginCredential)
console.log(config)
const resp = await axios.post( const resp = await axios.post(
configs.API_URL + '/users/login', configs.API_URL + '/auth/login',
loginCredential, loginCredential,
config config
) )
if (resp.data.token) {
const token = resp.data.token; const token = resp.data.token;
await SecureStore.setItemAsync(key, token); await SecureStore.setItemAsync(key, token);
if (remember) {
await SecureStore.setItemAsync(keyRemember, remember.toString());
}
const headers = { const headers = {
'Authorization': 'Bearer ' + token 'Authorization': 'Bearer ' + token
}; };
const user = await axios.get( const user = await axios.get(
configs.API_URL + '/users', configs.API_URL + '/user',
{ headers } { headers }
) )
dispatch(setLoginState(user.data)); dispatch(userLogin(UserMapper.toModel(user.data.data)));
} else {
console.log('Login Failed', 'Username or Password is incorrect'); } catch (error: any) {
console.error("Error : " + error.message);
switch (error.response.status) {
case 400:
dispatch(setErrorLogin(true)); dispatch(setErrorLogin(true));
} break;
} catch (error) { default:
if (axios.isAxiosError(error)) {
console.log("axios : " + error.message);
dispatch(setErrorNetwork(true)); dispatch(setErrorNetwork(true));
} else { break;
dispatch(setErrorLogin(true));
} }
} }
} }
@ -94,45 +102,60 @@ export const login = (loginCredential: LoginCredentials) => {
export const getRefreshToken = () => { export const getRefreshToken = () => {
//@ts-ignore //@ts-ignore
return async dispatch => { return async dispatch => {
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 { try {
let userToken: string | null = await SecureStore.getItemAsync(key); const user = await axios.get(
configs.API_URL + '/user',
if (userToken) { { headers }
dispatch(restoreToken(userToken)); )
await dispatch(userLogin(UserMapper.toModel(user.data.data)));
} catch (error: any) {
await SecureStore.deleteItemAsync(key);
dispatch(userLogout());
}
} else { } else {
const empty = ""; await SecureStore.deleteItemAsync(key);
dispatch(restoreToken(empty)); dispatch(userLogout());
} }
} catch (e) {
console.log('Error :', e);
} }
dispatch(restoreToken());
} }
} }
export const deleteUser = () => {
export const deleteToken = () => {
//@ts-ignore //@ts-ignore
return async dispatch => { return async dispatch => {
let token: string | null = await SecureStore.getItemAsync(key);
if (token) {
const headers = {
'Authorization': 'Bearer ' + token
};
try { try {
await axios.delete(
configs.API_URL + '/user',
{ headers }
)
await SecureStore.deleteItemAsync(key); await SecureStore.deleteItemAsync(key);
dispatch(userLogout()); dispatch(userLogout());
} catch (e) { } catch (error: any) {
console.log('Error deleting token', e); console.error("Error deleting account : " + error.message);
} }
} }
} }
export const darkMode = (value: boolean) => {
//@ts-ignore
return async dispatch => {
dispatch(setDarkMode(value));
}
} }
export const imageUserCurrent = (value: any) => { export const logout = () => {
//@ts-ignore //@ts-ignore
return async dispatch => { return async dispatch => {
//@ts-ignore await SecureStore.deleteItemAsync(key);
dispatch(setImageUserCurrent(value)); await SecureStore.deleteItemAsync(keyRemember);
dispatch(userLogout());
} }
} }

@ -1,5 +1,5 @@
import axios from "axios"; import axios from "axios";
import { Spot } from "../../model/Spot"; import { Spot } from "../../models/Spot";
export const likeSpot = async (spot: Spot) => { export const likeSpot = async (spot: Spot) => {
return async (dispatch) => { return async (dispatch) => {

@ -1,6 +1,6 @@
import axios from "axios"; import axios from "axios";
import * as SecureStore from 'expo-secure-store'; 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 SpotifyService from "../../services/spotify/spotify.service";
import { setSpotList, setUserCurrentMusic } from "../actions/spotActions"; import { setSpotList, setUserCurrentMusic } from "../actions/spotActions";
const key = 'userToken'; const key = 'userToken';

@ -0,0 +1,8 @@
import { setDarkMode } from "../actions/userActions";
export const darkMode = (value: boolean) => {
//@ts-ignore
return async dispatch => {
dispatch(setDarkMode(value));
}
}

@ -5,7 +5,7 @@ import Animated, { interpolate, SensorType, useAnimatedSensor, useAnimatedStyle,
import { Audio } from 'expo-av'; import { Audio } from 'expo-av';
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import normalize from '../components/Normalize'; import normalize from '../components/Normalize';
import Music from "../model/Music"; import Music from "../models/Music";
import SpotifyService from "../services/spotify/spotify.service"; import SpotifyService from "../services/spotify/spotify.service";
import { LinearGradient } from "expo-linear-gradient"; import { LinearGradient } from "expo-linear-gradient";
import FontAwesome from 'react-native-vector-icons/FontAwesome'; import FontAwesome from 'react-native-vector-icons/FontAwesome';

@ -2,7 +2,8 @@ import React from 'react';
import { StyleSheet, Text, View, FlatList, TouchableHighlight, SafeAreaView } from 'react-native'; import { StyleSheet, Text, View, FlatList, TouchableHighlight, SafeAreaView } from 'react-native';
import CardMusic from '../components/CardMusicComponent'; import CardMusic from '../components/CardMusicComponent';
import normalize from '../components/Normalize'; 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 FladyComponent from '../components/FladyComponent';
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
@ -38,6 +39,12 @@ export default function FavoriteScreen() {
marginTop: 10, marginTop: 10,
marginLeft: 20, marginLeft: 20,
}, },
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingRight: 20
},
title: { title: {
fontSize: normalize(28), fontSize: normalize(28),
fontWeight: 'bold', fontWeight: 'bold',
@ -78,7 +85,13 @@ export default function FavoriteScreen() {
return ( return (
<SafeAreaView style={styles.mainSafeArea}> <SafeAreaView style={styles.mainSafeArea}>
<View style={styles.titleContainer}> <View style={styles.titleContainer}>
<View style={styles.header}>
<Text style={styles.title}>Favoris</Text> <Text style={styles.title}>Favoris</Text>
<Svg width="37" height="32" viewBox="0 0 37 32">
<Path d="M9.39205 6.37931L14.599 18.0303C12.9892 17.8669 11.2806 18.2871 9.86635 19.6221C8.33267 21.0923 7.65381 23.3411 8.17936 25.3951C9.22118 29.409 13.8077 31.1848 17.2659 28.9363C19.748 27.3202 20.5459 24.0209 19.334 21.3091L13.3354 7.88669L16.0608 6.66869C17.5597 5.9988 18.238 4.22429 17.5681 2.72534C16.8983 1.22639 15.1237 0.548067 13.6248 1.21796L10.8994 2.43595C9.40048 3.10585 8.72216 4.88036 9.39205 6.37931Z" fill={style.Text} />
<Path fill-rule="evenodd" clip-rule="evenodd" d="M22.6902 6.94631C24.9408 6.17968 27.2985 7.50221 27.8347 9.85848L27.9409 10.3251C27.9935 10.5559 28.1538 10.7473 28.3718 10.8396C28.5898 10.9319 28.8388 10.9137 29.0411 10.7907L29.45 10.5421C31.5149 9.28682 34.1056 10.0587 35.122 12.2081C36.1586 14.4001 35.1314 17.1073 32.8907 18.0685L23.6308 22.041L20.0364 12.6279C19.1666 10.3502 20.3949 7.72815 22.6902 6.94631ZM18.661 13.1531L22.2742 22.6155C22.5641 23.3744 23.4162 23.735 24.1628 23.4148L33.4711 19.4215C36.4742 18.1332 37.8481 14.5289 36.453 11.5787C35.1306 8.78232 31.8557 7.69783 29.1345 9.03746C28.2019 6.15137 25.1435 4.55531 22.2155 5.5527C19.1263 6.60497 17.4953 10.1004 18.661 13.1531Z" fill={style.Text} />
</Svg>
</View>
<Text style={styles.description}>Retrouvez ici vos musiques favorites</Text> <Text style={styles.description}>Retrouvez ici vos musiques favorites</Text>
</View> </View>
<FlatList <FlatList

@ -23,9 +23,9 @@ export default function LoginScreen() {
const failedLogin = useSelector(state => state.userReducer.failedLogin); const failedLogin = useSelector(state => state.userReducer.failedLogin);
// @ts-ignore // @ts-ignore
const networkError = useSelector(state => state.userReducer.errorNetwork); const networkError = useSelector(state => state.userReducer.errorNetwork);
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const dispatch = useDispatch();
async function playSound() { async function playSound() {
const { sound } = await Audio.Sound.createAsync( const { sound } = await Audio.Sound.createAsync(
@ -34,15 +34,14 @@ export default function LoginScreen() {
setSound(sound); setSound(sound);
await sound.playAsync(); await sound.playAsync();
} }
const dispatch = useDispatch();
const submitForm = () => { const submitForm = () => {
const credentials: LoginCredentials = { const credentials: LoginCredentials = {
email: username.toLowerCase(), email: username.toLowerCase(),
password: password.toLowerCase() password: password
}; };
//@ts-ignore //@ts-ignore
dispatch(login(credentials)) dispatch(login(credentials, rememberMe))
playSound() playSound()
} }
@ -62,7 +61,7 @@ export default function LoginScreen() {
{ cancelable: false } { cancelable: false }
); );
} }
}, [networkError, dispatch]); }, [networkError]);
const toggleRememberMe = () => { const toggleRememberMe = () => {
setRememberMe(!rememberMe); setRememberMe(!rememberMe);
@ -96,7 +95,11 @@ export default function LoginScreen() {
<Image source={require('../assets/images/lock_icon.png')} style={styles.iconLock} /> <Image source={require('../assets/images/lock_icon.png')} style={styles.iconLock} />
</View> </View>
<View style={styles.rememberMeContainer}> <View style={styles.rememberMeContainer}>
<TouchableOpacity style={[styles.checkbox, rememberMe ? styles.checkboxChecked : null]} onPress={toggleRememberMe}></TouchableOpacity> <TouchableOpacity style={[styles.checkbox, rememberMe ? styles.checkboxChecked : null]} onPress={toggleRememberMe}>
{rememberMe && (
<Image source={require("../assets/images/ok_icon.png")} style={styles.checkBoxImage} />
)}
</TouchableOpacity>
<Text style={styles.rememberMeText}>SE SOUVENIR DE MOI</Text> <Text style={styles.rememberMeText}>SE SOUVENIR DE MOI</Text>
</View> </View>
<TouchableOpacity style={[styles.button, styles.shadow]} onPress={submitForm}> <TouchableOpacity style={[styles.button, styles.shadow]} onPress={submitForm}>
@ -154,6 +157,10 @@ const styles = StyleSheet.create({
width: normalize(46), width: normalize(46),
height: normalize(46), height: normalize(46),
}, },
checkBoxImage: {
width: normalize(14),
height: normalize(11),
},
iconUser: { iconUser: {
position: 'absolute', position: 'absolute',
width: 20, width: 20,
@ -225,7 +232,12 @@ const styles = StyleSheet.create({
color: 'white' color: 'white'
}, },
checkboxChecked: { checkboxChecked: {
backgroundColor: 'white' backgroundColor: '#5C1DC3',
borderColor: '#5C1DC3',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'center'
}, },
inscriptionText: { inscriptionText: {
flexDirection: 'row', flexDirection: 'row',

@ -1,16 +1,16 @@
import React, { useEffect } from 'react'; import React from 'react';
import { View, Text, StyleSheet, TouchableWithoutFeedback, Keyboard, ScrollView, Image } from 'react-native'; import { Alert, View, Text, StyleSheet, TouchableWithoutFeedback, Keyboard, ScrollView, Image } from 'react-native';
import { TextInput, TouchableOpacity } from 'react-native-gesture-handler'; import { TextInput, TouchableOpacity } from 'react-native-gesture-handler';
import { Svg, Path } from 'react-native-svg'; import { Svg, Path } from 'react-native-svg';
import Modal from "react-native-modal"; import Modal from "react-native-modal";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import normalize from '../components/Normalize'; import normalize from '../components/Normalize';
import * as ImagePicker from 'expo-image-picker'; import * as ImagePicker from 'expo-image-picker';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { colorsDark } from '../constants/colorsDark'; import { colorsDark } from '../constants/colorsDark';
import { colorsLight } from '../constants/colorsLight'; import { colorsLight } from '../constants/colorsLight';
import { deleteUser } from '../redux/thunk/authThunk';
// @ts-ignore // @ts-ignore
const DismissKeyboard = ({ children }) => ( const DismissKeyboard = ({ children }) => (
@ -23,17 +23,33 @@ export default function ProfilScreen() {
// @ts-ignore // @ts-ignore
const isDark = useSelector(state => state.userReducer.dark); const isDark = useSelector(state => state.userReducer.dark);
// @ts-ignore // @ts-ignore
const UserCurrent = useSelector(state => state.userReducer.user); const userCurrent = useSelector(state => state.userReducer.user);
const style = isDark ? colorsDark : colorsLight; const style = isDark ? colorsDark : colorsLight;
const navigation = useNavigation(); const navigation = useNavigation();
const [isModalVisible, setIsModalVisible] = React.useState(false); const [isModalVisible, setIsModalVisible] = React.useState(false);
const dispatch = useDispatch();
useEffect(() => {
console.log(UserCurrent.image);
});
const handleModal = () => setIsModalVisible(() => !isModalVisible); 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 () => { const pickImage = async () => {
await ImagePicker.launchImageLibraryAsync({ await ImagePicker.launchImageLibraryAsync({
@ -56,8 +72,8 @@ export default function ProfilScreen() {
backgroundColor: style.body, backgroundColor: style.body,
}, },
buttonSetting: { buttonSetting: {
width: normalize(17), width: normalize(11),
height: normalize(17), height: normalize(18),
marginRight: 5 marginRight: 5
}, },
modalContent: { modalContent: {
@ -257,7 +273,7 @@ export default function ProfilScreen() {
// @ts-ignore // @ts-ignore
onPress={() => navigation.navigate('Setting')}> onPress={() => navigation.navigate('Setting')}>
<View style={styles.exit}> <View style={styles.exit}>
<Image style={styles.buttonSetting} source={require('../assets/images/chevron_right_icon.png')} /> <Image style={styles.buttonSetting} source={require('../assets/images/chevron_left_icon.png')} />
<Text style={styles.textExit}>Exit</Text> <Text style={styles.textExit}>Exit</Text>
</View> </View>
</TouchableOpacity> </TouchableOpacity>
@ -265,18 +281,18 @@ export default function ProfilScreen() {
<Text style={styles.title}>Profil</Text> <Text style={styles.title}>Profil</Text>
<TouchableOpacity onPress={pickImage} > <TouchableOpacity onPress={pickImage} >
<View style={styles.imageWrapper}> <View style={styles.imageWrapper}>
<Image source={{ uri: UserCurrent.image }} style={styles.imageProfil} /> <Image source={{ uri: userCurrent.image }} style={styles.imageProfil} />
</View> </View>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={styles.body}> <View style={styles.body}>
<View style={styles.optionId}> <View style={styles.optionId}>
<Text style={styles.textOption}>Identifiant</Text> <Text style={styles.textOption}>Identifiant</Text>
<TextInput placeholderTextColor='#828288' placeholder={UserCurrent.name} style={styles.textInputId} /> <TextInput placeholderTextColor='#828288' placeholder={userCurrent.name} style={styles.textInputId} />
</View> </View>
<View style={styles.optionMail}> <View style={styles.optionMail}>
<Text style={styles.textOption}>Mail</Text> <Text style={styles.textOption}>Mail</Text>
<TextInput placeholderTextColor='#828288' placeholder={UserCurrent.email} style={styles.textInputMail} /> <TextInput placeholderTextColor='#828288' placeholder={userCurrent.email} style={styles.textInputMail} />
</View> </View>
</View> </View>
@ -295,16 +311,11 @@ export default function ProfilScreen() {
<View style={styles.deleteOption}> <View style={styles.deleteOption}>
<View style={styles.buttonDeleteOption}> <View style={styles.buttonDeleteOption}>
<Svg width="20" height="22" viewBox="0 0 25 31"> <Svg width="20" height="20" viewBox="0 0 29 29">
<Path d="M21.4265 16.0194V21.7371V28.4078L19.8044 29.8373H10.6125L21.4265 16.0194Z" fill="#686868" /> <Path d="M10.8157 22.6797C10.3586 22.6797 10.0657 22.4101 10.0422 21.9648L9.69067 9.0625C9.67895 8.62891 9.97192 8.34765 10.4407 8.34765C10.8743 8.34765 11.179 8.61719 11.1907 9.05078L11.5657 21.9648C11.5774 22.3984 11.2727 22.6797 10.8157 22.6797ZM14.3899 22.6797C13.9328 22.6797 13.6164 22.3984 13.6164 21.9648V9.0625C13.6164 8.62891 13.9328 8.34765 14.3899 8.34765C14.8469 8.34765 15.175 8.62891 15.175 9.0625V21.9648C15.175 22.3984 14.8469 22.6797 14.3899 22.6797ZM17.9758 22.6797C17.5188 22.6797 17.2141 22.3984 17.2258 21.9648L17.5891 9.0625C17.6008 8.61719 17.9055 8.34765 18.3391 8.34765C18.8078 8.34765 19.1008 8.62891 19.0891 9.0625L18.7375 21.9648C18.7141 22.4101 18.4211 22.6797 17.9758 22.6797ZM9.24536 5.5H11.1086V2.99219C11.1086 2.32422 11.5774 1.89062 12.2805 1.89062H16.4758C17.1789 1.89062 17.6477 2.32422 17.6477 2.99219V5.5H19.5109V2.875C19.5109 1.17578 18.4094 0.144531 16.6047 0.144531H12.1516C10.3469 0.144531 9.24536 1.17578 9.24536 2.875V5.5ZM3.92505 6.4375H24.8664C25.3469 6.4375 25.7336 6.02734 25.7336 5.54687C25.7336 5.06641 25.3469 4.66797 24.8664 4.66797H3.92505C3.4563 4.66797 3.04614 5.06641 3.04614 5.54687C3.04614 6.03906 3.4563 6.4375 3.92505 6.4375ZM9.0227 26.2422H19.7688C21.4445 26.2422 22.5695 25.1523 22.6516 23.4765L23.4719 6.19141H5.30786L6.13989 23.4883C6.22192 25.164 7.32348 26.2422 9.0227 26.2422Z" fill="white" fill-opacity="0.85" />
<Path d="M9.41089 3.4031V1H15.4186V3.4031" stroke="white" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" />
<Path d="M21.4264 8.81006V27.4341C21.4264 28.7613 20.3504 29.8372 19.0233 29.8372H5.80618C4.47901 29.8372 3.40308 28.7613 3.40308 27.4341V8.81006" stroke="white" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" />
<Path d="M1 3.40308H23.8295V5.80618H1V3.40308Z" stroke="white" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" />
<Path d="M15.4185 10.7626V26.8333" stroke="white" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" />
<Path d="M9.41089 10.7626V26.8333" stroke="white" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" />
</Svg> </Svg>
</View> </View>
<TouchableOpacity onPress={() => console.log("Tkt t deconnecter")}> <TouchableOpacity onPress={() => deleteAccount()}>
<Text style={styles.textDeleteOption}>Supprimer le compte</Text> <Text style={styles.textDeleteOption}>Supprimer le compte</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>

@ -1,16 +1,15 @@
import React, { useEffect, useState } from 'react'; import React, { useState } from 'react';
import { View, Image, StyleSheet, Text, ImageBackground, TextInput, TouchableWithoutFeedback, Keyboard, TouchableOpacity, Platform } from 'react-native'; import { Alert, View, Image, StyleSheet, Text, ImageBackground, TextInput, TouchableWithoutFeedback, Keyboard, TouchableOpacity, Platform } from 'react-native';
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import normalize from '../components/Normalize'; import normalize from '../components/Normalize';
import * as SecureStore from 'expo-secure-store';
import * as AuthSession from 'expo-auth-session'; import * as AuthSession from 'expo-auth-session';
import { register } from '../redux/thunk/authThunk'; import { register } from '../redux/thunk/authThunk';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Audio } from 'expo-av'; import { Audio } from 'expo-av';
import { RegisterCredentials } from '../redux/actions/userActions'; import { RegisterCredentials } from '../redux/actions/userActions';
import * as WebBrowser from 'expo-web-browser';
import { setSpotList } from '../redux/actions/spotActions'; import { setSpotList } from '../redux/actions/spotActions';
import { spotsData } from '../data/data'; import { spotsData } from '../data/data';
import configs from '../constants/config';
// @ts-ignore // @ts-ignore
const DismissKeyboard = ({ children }) => ( const DismissKeyboard = ({ children }) => (
@ -19,21 +18,18 @@ const DismissKeyboard = ({ children }) => (
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
) )
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() { export default function RegisterScreen() {
const [sound, setSound] = useState<Audio.Sound>(); const [sound, setSound] = useState<Audio.Sound>();
const navigation = useNavigation(); 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 // @ts-ignore
const failedSignup = useSelector(state => state.userReducer.failedSignup); const failedSignup = useSelector(state => state.userReducer.failedSignup);
// @ts-ignore
const errorMessage = useSelector(state => state.userReducer.errorMessage);
async function playSound() { async function playSound() {
const { sound } = await Audio.Sound.createAsync( const { sound } = await Audio.Sound.createAsync(
@ -42,24 +38,42 @@ export default function RegisterScreen() {
setSound(sound); setSound(sound);
await sound.playAsync(); await sound.playAsync();
} }
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const dispatch = useDispatch();
function addMockSpots() { function addMockSpots() {
dispatch(setSpotList(spotsData)) dispatch(setSpotList(spotsData))
} }
const submitForm = () => { 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 = { const credentials: RegisterCredentials = {
email: email, email: email,
password: password, password: password,
idSpotify: spotifyToken, tokenSpotify: spotifyToken,
name: username, name: username
idFlad: generateRandomString()
}; };
//@ts-ignore //@ts-ignore
@ -68,35 +82,21 @@ export default function RegisterScreen() {
playSound() playSound()
} }
function generateRandomString(): string { const getSpotifyToken = async () => {
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 () => {
try { try {
const redirectUri = AuthSession.makeRedirectUri(); const redirectUri = AuthSession.makeRedirectUri();
const result = await AuthSession.startAsync({ const result: any = await AuthSession.startAsync({
authUrl: 'https://flad-api-production.up.railway.app/api/spotify/exchange?' + '&redirectUrl=' + authUrl: configs.API_URL + '/spotify/exchange?' + 'redirectUrl=' +
encodeURIComponent(redirectUri) encodeURIComponent(redirectUri)
}) })
const { const {
access_token: access_token, access_token: access_token,
refresh_token: refresh_token, refresh_token: refresh_token,
} = result.params } = result.params
save(MY_SECURE_AUTH_STATE_KEY, access_token); setSpotifyToken(refresh_token)
setSpotifyToken(access_token) } catch (error) {
save(MY_SECURE_AUTH_STATE_KEY_REFRESH, refresh_token); Alert.alert("Erreur inscription", "La connexion à Spotify à échouer.");
} catch (err) { return;
console.error(err);
} }
} }
@ -111,7 +111,7 @@ export default function RegisterScreen() {
<Image source={require("../assets/images/flad_logo.png")} style={styles.imageLogo} /> <Image source={require("../assets/images/flad_logo.png")} style={styles.imageLogo} />
<Text style={styles.text}>S'INSCRIRE</Text> <Text style={styles.text}>S'INSCRIRE</Text>
{failedSignup && ( {failedSignup && (
<Text style={styles.textError}>Email ou mot de passe incorrect!</Text> <Text style={styles.textError}>{errorMessage}</Text>
)} )}
<View style={{ marginTop: 7 }}> <View style={{ marginTop: 7 }}>
<TextInput style={[styles.input, styles.shadow]} placeholder="Username" <TextInput style={[styles.input, styles.shadow]} placeholder="Username"
@ -135,12 +135,15 @@ export default function RegisterScreen() {
<Image source={require('../assets/images/lock_icon.png')} style={styles.iconLock} /> <Image source={require('../assets/images/lock_icon.png')} style={styles.iconLock} />
</View> </View>
<TouchableOpacity onPress={async () => { <TouchableOpacity onPress={async () => {
await getTokens2(); await getSpotifyToken();
}} style={[styles.buttonSpotify, styles.shadow]}> }} style={[styles.buttonSpotify, styles.shadow]}>
<Text style={styles.textIntoButton}>Lier compte</Text> <Text style={styles.textIntoButton}>Lier compte</Text>
{spotifyToken == null ? (
<Image source={require("../assets/images/spotify_icon.png")} style={{ width: normalize(35), height: normalize(35) }} /> <Image source={require("../assets/images/spotify_icon.png")} style={{ width: normalize(35), height: normalize(35) }} />
) :
<Image source={require("../assets/images/ok_icon.png")} style={{ width: normalize(17), height: normalize(14) }} />
}
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity style={[styles.button, styles.shadow]} onPress={() => submitForm()}> <TouchableOpacity style={[styles.button, styles.shadow]} onPress={() => submitForm()}>
<Image source={require("../assets/images/arrow_forward.png")} style={styles.buttonImage} /> <Image source={require("../assets/images/arrow_forward.png")} style={styles.buttonImage} />
</TouchableOpacity> </TouchableOpacity>

@ -7,9 +7,11 @@ import { useDispatch, useSelector } from 'react-redux';
import normalize from '../components/Normalize'; import normalize from '../components/Normalize';
import { ScrollView, Switch, TextInput } from 'react-native-gesture-handler'; import { ScrollView, Switch, TextInput } from 'react-native-gesture-handler';
import CardMusic from '../components/CardMusicComponent'; 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 { colorsDark } from '../constants/colorsDark';
import { colorsLight } from '../constants/colorsLight'; import { colorsLight } from '../constants/colorsLight';
import { User } from '../models/User';
// @ts-ignore // @ts-ignore
const DismissKeyboard = ({ children }) => ( const DismissKeyboard = ({ children }) => (
@ -30,44 +32,36 @@ export default function SettingScreen() {
// @ts-ignore // @ts-ignore
const currentMusic = useSelector(state => state.appReducer.userCurrentMusic); const currentMusic = useSelector(state => state.appReducer.userCurrentMusic);
// @ts-ignore
const currentUser: User = useSelector(state => state.userReducer.user);
// @ts-ignore // @ts-ignore
const isDark = useSelector(state => state.userReducer.dark); const isDark = useSelector(state => state.userReducer.dark);
const style = isDark ? colorsDark : colorsLight; const style = isDark ? colorsDark : colorsLight;
async function ChangeDarkMode() { async function ChangeDarkMode() {
try { const newValue = !isDark;
const currentValue = await AsyncStorage.getItem('dark'); await AsyncStorage.setItem('dark', newValue.toString());
if (currentValue !== null) {
const newValue = JSON.stringify(!JSON.parse(currentValue));
await AsyncStorage.setItem('dark', newValue);
// @ts-ignore // @ts-ignore
dispatch(ChangeMode(JSON.parse(newValue))) dispatch(darkMode(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);
}
} }
//Notification
const [isCheckedNotif, setIsCheckedNotif] = useState(false); const [isCheckedNotif, setIsCheckedNotif] = useState(false);
const toggleNotif = const toggleNotif =
() => setIsCheckedNotif(value => !value); () => setIsCheckedNotif(value => !value);
//Deconnection
const Deconnection = () => { const Deconnection = () => {
//@ts-ignore //@ts-ignore
dispatch(DeleteToken()) dispatch(logout())
} }
//Localisation
const [isCheckedLocalisation, setIsCheckedLocalisation] = useState(false); const [isCheckedLocalisation, setIsCheckedLocalisation] = useState(false);
const toggleLocalisation = const toggleLocalisation =
() => setIsCheckedLocalisation(value => !value); () => setIsCheckedLocalisation(value => !value);
//Style
const styles = StyleSheet.create({ const styles = StyleSheet.create({
mainSafeArea: { mainSafeArea: {
flex: 1, flex: 1,
@ -110,6 +104,7 @@ export default function SettingScreen() {
imageProfil: { imageProfil: {
marginLeft: 15, marginLeft: 15,
marginRight: 7, marginRight: 7,
borderRadius: 8,
width: 50, width: 50,
height: 50 height: 50
}, },
@ -129,8 +124,8 @@ export default function SettingScreen() {
justifyContent: 'center', justifyContent: 'center',
}, },
buttonSetting: { buttonSetting: {
width: normalize(17), width: normalize(11),
height: normalize(17), height: normalize(18),
marginRight: 22 marginRight: 22
}, },
body: { body: {
@ -260,7 +255,7 @@ export default function SettingScreen() {
</Svg> </Svg>
</TouchableOpacity> </TouchableOpacity>
<TextInput placeholderTextColor="#828288" ref={textInputRef} placeholder='Recherche' style={styles.inputSearch}></TextInput> <TextInput placeholderTextColor="#828288" ref={textInputRef} placeholder='Recherche' style={styles.inputSearch}></TextInput>
<Svg width="19" height="19" viewBox="0 0 19 19" fill="none"> <Svg width="19" height="19" viewBox="0 0 19 19">
<Path d="M13.6563 8.3125V10.0938C13.6563 11.1961 13.2184 12.2532 12.4389 13.0327C11.6595 13.8121 10.6023 14.25 9.5 14.25C8.39769 14.25 7.34054 13.8121 6.56109 13.0327C5.78164 12.2532 5.34375 11.1961 5.34375 10.0938V8.3125H4.15625V10.0938C4.15687 11.4078 4.64161 12.6755 5.51785 13.6547C6.39409 14.6339 7.60038 15.2559 8.90625 15.4019V16.625H6.53125V17.8125H12.4688V16.625H10.0938V15.4019C11.3996 15.2559 12.6059 14.6339 13.4822 13.6547C14.3584 12.6755 14.8431 11.4078 14.8438 10.0938V8.3125H13.6563Z" fill="#828288" /> <Path d="M13.6563 8.3125V10.0938C13.6563 11.1961 13.2184 12.2532 12.4389 13.0327C11.6595 13.8121 10.6023 14.25 9.5 14.25C8.39769 14.25 7.34054 13.8121 6.56109 13.0327C5.78164 12.2532 5.34375 11.1961 5.34375 10.0938V8.3125H4.15625V10.0938C4.15687 11.4078 4.64161 12.6755 5.51785 13.6547C6.39409 14.6339 7.60038 15.2559 8.90625 15.4019V16.625H6.53125V17.8125H12.4688V16.625H10.0938V15.4019C11.3996 15.2559 12.6059 14.6339 13.4822 13.6547C14.3584 12.6755 14.8431 11.4078 14.8438 10.0938V8.3125H13.6563Z" fill="#828288" />
<Path d="M9.5 13.0625C10.2874 13.0625 11.0425 12.7497 11.5992 12.193C12.156 11.6362 12.4688 10.8811 12.4688 10.0938V4.15625C12.4688 3.36889 12.156 2.61378 11.5992 2.05703C11.0425 1.50028 10.2874 1.1875 9.5 1.1875C8.71264 1.1875 7.95753 1.50028 7.40078 2.05703C6.84403 2.61378 6.53125 3.36889 6.53125 4.15625V10.0938C6.53125 10.8811 6.84403 11.6362 7.40078 12.193C7.95753 12.7497 8.71264 13.0625 9.5 13.0625Z" fill="#828288" /> <Path d="M9.5 13.0625C10.2874 13.0625 11.0425 12.7497 11.5992 12.193C12.156 11.6362 12.4688 10.8811 12.4688 10.0938V4.15625C12.4688 3.36889 12.156 2.61378 11.5992 2.05703C11.0425 1.50028 10.2874 1.1875 9.5 1.1875C8.71264 1.1875 7.95753 1.50028 7.40078 2.05703C6.84403 2.61378 6.53125 3.36889 6.53125 4.15625V10.0938C6.53125 10.8811 6.84403 11.6362 7.40078 12.193C7.95753 12.7497 8.71264 13.0625 9.5 13.0625Z" fill="#828288" />
</Svg> </Svg>
@ -271,9 +266,9 @@ export default function SettingScreen() {
onPress={() => navigation.navigate('Profil')} onPress={() => navigation.navigate('Profil')}
> >
<View style={styles.profil}> <View style={styles.profil}>
<Image source={require('../assets/images/profil_icon_asupp.png')} style={styles.imageProfil} /> <Image source={{ uri: currentUser.image }} style={styles.imageProfil} />
<View style={styles.profilContainer}> <View style={styles.profilContainer}>
<Text style={styles.NameProfil}>Emre KARTAL</Text> <Text style={styles.NameProfil}>{currentUser.name}</Text>
<Text style={styles.description}>id. Spotify, mail et mot de passe</Text> <Text style={styles.description}>id. Spotify, mail et mot de passe</Text>
</View> </View>
<Image style={styles.buttonSetting} source={require('../assets/images/chevron_right_icon.png')} /> <Image style={styles.buttonSetting} source={require('../assets/images/chevron_right_icon.png')} />
@ -283,10 +278,9 @@ export default function SettingScreen() {
<View style={styles.body}> <View style={styles.body}>
<View style={styles.Option}> <View style={styles.Option}>
<View style={styles.view}> <View style={styles.view}>
<Svg width="23" height="22" viewBox="0 0 23 18" fill="none"> <Svg width="22" height="22" viewBox="0 0 29 29">
<Path d="M1 8.63636C1 8.63636 4.81818 1 11.5 1C18.1818 1 22 8.63636 22 8.63636" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <Path d="M14.0183 26.0157C20.5574 26.0157 25.9714 20.6016 25.9714 14.0625C25.9714 7.53516 20.5457 2.10938 14.0066 2.10938C7.47925 2.10938 2.06519 7.53516 2.06519 14.0625C2.06519 20.6016 7.49097 26.0157 14.0183 26.0157Z" fill="white" fill-opacity="0.85" />
<Path d="M1 8.63635C1 8.63635 4.81818 16.2727 11.5 16.2727C18.1818 16.2727 22 8.63635 22 8.63635" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <Path d="M14.3464 21.0938C10.3855 21.0938 7.33862 18.2579 7.33862 14.2735C7.33862 11.4375 9.0144 8.74218 11.5222 7.74609C11.6746 7.68749 11.8386 7.66406 11.9207 7.66406C12.1668 7.66406 12.3074 7.87499 12.3074 8.06249C12.3074 8.12109 12.3074 8.21484 12.2371 8.36718C12.0144 8.9414 11.8152 10.0078 11.8152 10.7812C11.8152 14.4375 14.1472 16.7344 17.8152 16.7344C18.6472 16.7344 19.5027 16.5469 20.0066 16.3829C20.1355 16.3477 20.2175 16.336 20.2996 16.336C20.4988 16.336 20.698 16.4883 20.698 16.7461C20.698 16.8047 20.6863 16.9688 20.616 17.1211C19.7605 19.3125 17.2644 21.0938 14.3464 21.0938Z" fill="#fe9500" />
<Path d="M11.4997 11.5C13.0813 11.5 14.3634 10.2179 14.3634 8.63634C14.3634 7.0548 13.0813 5.77271 11.4997 5.77271C9.9182 5.77271 8.63611 7.0548 8.63611 8.63634C8.63611 10.2179 9.9182 11.5 11.4997 11.5Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</Svg> </Svg>
</View> </View>
<View style={styles.firstOptionView}> <View style={styles.firstOptionView}>
@ -296,7 +290,7 @@ export default function SettingScreen() {
</View> </View>
<View style={styles.secondOption}> <View style={styles.secondOption}>
<View style={styles.notification}> <View style={styles.notification}>
<Svg width="23" height="22" viewBox="0 0 1242 1242"> <Svg width="22" height="22" viewBox="0 0 1242 1242">
<Path d="M620.553 1181.81C642.27 1181.31 663.11 1173.14 679.388 1158.76C695.665 1144.37 706.331 1124.69 709.498 1103.2H528.159C531.416 1125.28 542.581 1145.42 559.577 1159.88C576.572 1174.34 598.241 1182.13 620.553 1181.81Z" fill="white" /> <Path d="M620.553 1181.81C642.27 1181.31 663.11 1173.14 679.388 1158.76C695.665 1144.37 706.331 1124.69 709.498 1103.2H528.159C531.416 1125.28 542.581 1145.42 559.577 1159.88C576.572 1174.34 598.241 1182.13 620.553 1181.81Z" fill="white" />
<Path d="M1132.51 969.785L1120.79 959.443C1087.53 929.815 1058.43 895.838 1034.25 858.43C1007.85 806.806 992.03 750.428 987.712 692.605V522.298C987.572 501.611 985.727 480.971 982.196 460.587C923.799 448.585 871.344 416.77 833.712 370.531C796.079 324.292 775.582 266.468 775.69 206.851C775.69 199.611 775.69 192.371 775.69 185.131C739.695 167.417 701.023 155.769 661.233 150.656V107.217C661.233 95.011 656.384 83.3046 647.752 74.6733C639.121 66.0421 627.415 61.1931 615.208 61.1931C603.002 61.1931 591.295 66.0421 582.664 74.6733C574.033 83.3046 569.184 95.011 569.184 107.217V152.38C480.09 164.948 398.552 209.324 339.622 277.315C280.692 345.307 248.349 432.323 248.565 522.298V692.605C244.247 750.428 228.424 806.806 202.024 858.43C178.266 895.745 149.628 929.716 116.87 959.443L105.149 969.785V1067.01H1132.51V969.785Z" fill="white" /> <Path d="M1132.51 969.785L1120.79 959.443C1087.53 929.815 1058.43 895.838 1034.25 858.43C1007.85 806.806 992.03 750.428 987.712 692.605V522.298C987.572 501.611 985.727 480.971 982.196 460.587C923.799 448.585 871.344 416.77 833.712 370.531C796.079 324.292 775.582 266.468 775.69 206.851C775.69 199.611 775.69 192.371 775.69 185.131C739.695 167.417 701.023 155.769 661.233 150.656V107.217C661.233 95.011 656.384 83.3046 647.752 74.6733C639.121 66.0421 627.415 61.1931 615.208 61.1931C603.002 61.1931 591.295 66.0421 582.664 74.6733C574.033 83.3046 569.184 95.011 569.184 107.217V152.38C480.09 164.948 398.552 209.324 339.622 277.315C280.692 345.307 248.349 432.323 248.565 522.298V692.605C244.247 750.428 228.424 806.806 202.024 858.43C178.266 895.745 149.628 929.716 116.87 959.443L105.149 969.785V1067.01H1132.51V969.785Z" fill="white" />
<Path d="M1034.25 379.226C1129.45 379.226 1206.63 302.051 1206.63 206.851C1206.63 111.65 1129.45 34.4751 1034.25 34.4751C939.053 34.4751 861.878 111.65 861.878 206.851C861.878 302.051 939.053 379.226 1034.25 379.226Z" fill="white" /> <Path d="M1034.25 379.226C1129.45 379.226 1206.63 302.051 1206.63 206.851C1206.63 111.65 1129.45 34.4751 1034.25 34.4751C939.053 34.4751 861.878 111.65 861.878 206.851C861.878 302.051 939.053 379.226 1034.25 379.226Z" fill="white" />
@ -309,7 +303,7 @@ export default function SettingScreen() {
</View> </View>
<View style={styles.lastOption}> <View style={styles.lastOption}>
<View style={styles.localisation}> <View style={styles.localisation}>
<Svg width="24" height="23" viewBox="0 0 472 420" fill="none"> <Svg width="22" height="22" viewBox="0 0 472 420">
<Path d="M235.735 0C178.774 0 132.601 46.1729 132.601 103.134C132.601 149.184 200.006 233.115 225.83 263.581C231.061 269.752 240.416 269.752 245.639 263.581C271.463 233.115 338.868 149.184 338.868 103.134C338.868 46.1729 292.696 0 235.735 0ZM235.735 137.512C216.745 137.512 201.357 122.124 201.357 103.134C201.357 84.1441 216.745 68.7559 235.735 68.7559C254.724 68.7559 270.112 84.1441 270.112 103.134C270.112 122.124 254.724 137.512 235.735 137.512ZM16.4687 176.76C11.6081 178.704 7.44144 182.06 4.50605 186.394C1.57065 190.729 0.00116411 195.843 0 201.078L0 405.971C0 415.237 9.35571 421.572 17.9584 418.134L130.964 366.698V175.917C123.728 162.837 117.81 150.101 113.57 137.921L16.4687 176.76ZM235.735 294.398C224.218 294.398 213.323 289.34 205.85 280.516C189.758 261.526 172.643 239.901 157.156 217.719V366.69L314.313 419.075V217.727C298.826 239.901 281.719 261.534 265.619 280.524C258.146 289.34 247.251 294.398 235.735 294.398ZM453.511 131.913L340.505 183.349V419.084L455 373.287C459.862 371.344 464.029 367.989 466.964 363.654C469.9 359.319 471.469 354.204 471.469 348.969V144.076C471.469 134.811 462.113 128.475 453.511 131.913Z" fill="white" /> <Path d="M235.735 0C178.774 0 132.601 46.1729 132.601 103.134C132.601 149.184 200.006 233.115 225.83 263.581C231.061 269.752 240.416 269.752 245.639 263.581C271.463 233.115 338.868 149.184 338.868 103.134C338.868 46.1729 292.696 0 235.735 0ZM235.735 137.512C216.745 137.512 201.357 122.124 201.357 103.134C201.357 84.1441 216.745 68.7559 235.735 68.7559C254.724 68.7559 270.112 84.1441 270.112 103.134C270.112 122.124 254.724 137.512 235.735 137.512ZM16.4687 176.76C11.6081 178.704 7.44144 182.06 4.50605 186.394C1.57065 190.729 0.00116411 195.843 0 201.078L0 405.971C0 415.237 9.35571 421.572 17.9584 418.134L130.964 366.698V175.917C123.728 162.837 117.81 150.101 113.57 137.921L16.4687 176.76ZM235.735 294.398C224.218 294.398 213.323 289.34 205.85 280.516C189.758 261.526 172.643 239.901 157.156 217.719V366.69L314.313 419.075V217.727C298.826 239.901 281.719 261.534 265.619 280.524C258.146 289.34 247.251 294.398 235.735 294.398ZM453.511 131.913L340.505 183.349V419.084L455 373.287C459.862 371.344 464.029 367.989 466.964 363.654C469.9 359.319 471.469 354.204 471.469 348.969V144.076C471.469 134.811 462.113 128.475 453.511 131.913Z" fill="white" />
</Svg> </Svg>
@ -335,14 +329,14 @@ export default function SettingScreen() {
<View style={styles.musicActually}> <View style={styles.musicActually}>
<CardMusic image={currentMusic.image} title={currentMusic.title} description={currentMusic.bio} id='1' /> <CardMusic image={currentMusic.image} title={currentMusic.title} description={currentMusic.bio} id='1' />
<Image source={require("../assets/images/flady_shadow.png")} style={styles.mascot} /> <Image source={require("../assets/images/flady_icon.png")} style={styles.mascot} />
</View> </View>
</> </>
) : <></>} ) : <></>}
<View style={styles.deconnectedOption}> <View style={styles.deconnectedOption}>
<View style={styles.buttonDeconectedOption}> <View style={styles.buttonDeconectedOption}>
<Svg width="23" height="24" viewBox="0 0 23 24"> <Svg width="23" height="24">
<Path d="M4.36916 23.102C4.03419 23.102 3.71379 22.9855 3.43707 22.7671L0.5243 20.3349C0.360787 20.2004 0.229004 20.0315 0.138369 19.8401C0.0477338 19.6488 0.000485206 19.4398 0 19.2281L0 3.87773C0 3.44081 0.18933 3.03302 0.5243 2.75631L3.43707 0.324142C3.87399 -0.0253914 4.47111 -0.0982107 4.98085 0.134811C5.23365 0.251805 5.44759 0.438845 5.59731 0.673748C5.74703 0.908652 5.82624 1.18157 5.82555 1.46012V21.6602C5.82624 21.9388 5.74703 22.2117 5.59731 22.4466C5.44759 22.6815 5.23365 22.8685 4.98085 22.9855C4.79152 23.0583 4.57306 23.102 4.36916 23.102ZM17.4767 15.9221V7.18373C17.4767 6.52835 18.2631 6.20795 18.7146 6.67399L22.574 10.5334C23.142 11.1014 23.142 12.0189 22.574 12.5869L18.7146 16.4463C18.6119 16.547 18.4817 16.615 18.3405 16.6418C18.1992 16.6686 18.0532 16.6529 17.9208 16.5969C17.7885 16.5408 17.6756 16.4468 17.5966 16.3267C17.5175 16.2066 17.4758 16.0658 17.4767 15.9221Z" fill="white" /> <Path d="M4.36916 23.102C4.03419 23.102 3.71379 22.9855 3.43707 22.7671L0.5243 20.3349C0.360787 20.2004 0.229004 20.0315 0.138369 19.8401C0.0477338 19.6488 0.000485206 19.4398 0 19.2281L0 3.87773C0 3.44081 0.18933 3.03302 0.5243 2.75631L3.43707 0.324142C3.87399 -0.0253914 4.47111 -0.0982107 4.98085 0.134811C5.23365 0.251805 5.44759 0.438845 5.59731 0.673748C5.74703 0.908652 5.82624 1.18157 5.82555 1.46012V21.6602C5.82624 21.9388 5.74703 22.2117 5.59731 22.4466C5.44759 22.6815 5.23365 22.8685 4.98085 22.9855C4.79152 23.0583 4.57306 23.102 4.36916 23.102ZM17.4767 15.9221V7.18373C17.4767 6.52835 18.2631 6.20795 18.7146 6.67399L22.574 10.5334C23.142 11.1014 23.142 12.0189 22.574 12.5869L18.7146 16.4463C18.6119 16.547 18.4817 16.615 18.3405 16.6418C18.1992 16.6686 18.0532 16.6529 17.9208 16.5969C17.7885 16.5408 17.6756 16.4468 17.5966 16.3267C17.5175 16.2066 17.4758 16.0658 17.4767 15.9221Z" fill="white" />
<Path d="M5.09735 3.54297H13.1075C13.5153 3.54297 13.8357 3.86337 13.8357 4.27116V7.91213M5.09735 19.5632H13.1075C13.5153 19.5632 13.8357 19.2428 13.8357 18.835V15.1941M21.8458 11.5531H10.1947" stroke="white" stroke-linecap="round" /> <Path d="M5.09735 3.54297H13.1075C13.5153 3.54297 13.8357 3.86337 13.8357 4.27116V7.91213M5.09735 19.5632H13.1075C13.5153 19.5632 13.8357 19.2428 13.8357 18.835V15.1941M21.8458 11.5531H10.1947" stroke="white" stroke-linecap="round" />
</Svg> </Svg>

Loading…
Cancel
Save