Front: Fix API calls with JWT Token
This commit is contained in:
@@ -4,3 +4,4 @@ POSTGRES_NAME=
|
||||
POSTGRES_HOST=
|
||||
DATABASE_URL=
|
||||
JWT_SECRET=
|
||||
API_URL=
|
||||
@@ -18,6 +18,7 @@ async function bootstrap() {
|
||||
SwaggerModule.setup('api', app, document);
|
||||
|
||||
app.useGlobalPipes(new ValidationPipe());
|
||||
app.enableCors();
|
||||
await app.listen(3000);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
+3
-1
@@ -21,6 +21,8 @@ services:
|
||||
front:
|
||||
build: ./front
|
||||
ports:
|
||||
- "8080:80"
|
||||
- "80:80"
|
||||
depends_on:
|
||||
- "back"
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
+142
-80
@@ -1,3 +1,4 @@
|
||||
import Artist from "./models/Artist";
|
||||
import AuthToken from "./models/AuthToken";
|
||||
import Chapter from "./models/Chapter";
|
||||
import Lesson from "./models/Lesson";
|
||||
@@ -5,51 +6,88 @@ import LessonHistory from "./models/LessonHistory";
|
||||
import Song from "./models/Song";
|
||||
import SongHistory from "./models/SongHistory";
|
||||
import User from "./models/User";
|
||||
import { translate } from "./i18n/i18n";
|
||||
import Constants from 'expo-constants';
|
||||
import store from "./state/Store";
|
||||
|
||||
const delay = (seconds: number) => new Promise(resolve => setTimeout(resolve, seconds * 1000));
|
||||
type AuthenticationInput = { username: string, password: string };
|
||||
type RegistrationInput = AuthenticationInput & { email: string };
|
||||
export type AccessToken = string;
|
||||
|
||||
declare type AuthenticationInput = { email: string, password: string };
|
||||
type FetchParams = {
|
||||
route: string;
|
||||
body?: Object;
|
||||
method?: 'GET' | 'POST' | 'DELETE'
|
||||
}
|
||||
|
||||
const dummyIllustration = "https://i.discogs.com/syRCX8NaLwK2SMk8X6TVU_DWc8RRqE4b-tebAQ6kVH4/rs:fit/g:sm/q:90/h:600/w:600/czM6Ly9kaXNjb2dz/LWRhdGFiYXNlLWlt/YWdlcy9SLTgyNTQz/OC0xNjE3ODE0NDI2/LTU1MjUuanBlZw.jpeg";
|
||||
|
||||
export default class API {
|
||||
|
||||
private static async fetch(params: FetchParams) {
|
||||
const jwtToken = store.getState().user.accessToken;
|
||||
const header = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
const response = await fetch(`${Constants.manifest?.extra?.apiUrl}${params.route}`, {
|
||||
headers: jwtToken && { ...header, 'Authorization': `Bearer ${jwtToken}` } || header,
|
||||
body: JSON.stringify(params.body),
|
||||
method: params.method ?? 'GET'
|
||||
});
|
||||
const jsonResponse = await response.json().catch(() => {
|
||||
throw new Error("Error while parsing Server's response");
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(jsonResponse.error ?? response.statusText)
|
||||
}
|
||||
return jsonResponse;
|
||||
}
|
||||
|
||||
public static async authenticate(authenticationInput: AuthenticationInput): Promise<AccessToken> {
|
||||
return API.fetch({
|
||||
route: '/auth/login',
|
||||
body: authenticationInput,
|
||||
method: 'POST'
|
||||
}).then((responseBody) => responseBody.access_token)
|
||||
}
|
||||
/**
|
||||
* Create a new user profile, with an email and a password
|
||||
* @param registrationInput the credentials to create a new profile
|
||||
* @returns A Promise. On success, will be resolved into an instance of the API wrapper
|
||||
*/
|
||||
public static async createAccount(registrationInput: RegistrationInput): Promise<AccessToken> {
|
||||
await API.fetch({
|
||||
route: '/auth/register',
|
||||
body: registrationInput,
|
||||
method: 'POST'
|
||||
});
|
||||
return API.authenticate(registrationInput);
|
||||
}
|
||||
|
||||
/***
|
||||
* Retrieve information of the currently authentified user
|
||||
*/
|
||||
static async getUserInfo(): Promise<User> {
|
||||
public static async getUserInfo(): Promise<User> {
|
||||
return API.fetch({
|
||||
route: '/auth/me'
|
||||
});
|
||||
}
|
||||
|
||||
public static async getUserSkills() {
|
||||
return {
|
||||
name: "User",
|
||||
email: "user@chromacase.com",
|
||||
xp: 2345,
|
||||
premium: false,
|
||||
metrics: {},
|
||||
settings: {},
|
||||
id: 1
|
||||
pedalsCompetency: Math.random() * 100,
|
||||
rightHandCompetency: Math.random() * 100,
|
||||
leftHandCompetency: Math.random() * 100,
|
||||
accuracyCompetency: Math.random() * 100,
|
||||
arpegeCompetency: Math.random() * 100,
|
||||
chordsCompetency: Math.random() * 100,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the user in, with an email and a password
|
||||
* @param _credentials the credentials to get an authentication token
|
||||
* @returns an authentication token, that must be used for authentified requests
|
||||
*/
|
||||
static async login(_credentials: AuthenticationInput): Promise<AuthToken> {
|
||||
return "12345";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new user profile, with an email and a password
|
||||
* @param _credentials the credentials to create a new profile
|
||||
* @returns an empty promise. On error, the promise will not be resolved
|
||||
*/
|
||||
static async register(_credentials: AuthenticationInput): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentify a new user through Google
|
||||
*/
|
||||
static async authWithGoogle(): Promise<AuthToken> {
|
||||
public static async authWithGoogle(): Promise<AuthToken> {
|
||||
//TODO
|
||||
return "11111";
|
||||
}
|
||||
|
||||
@@ -57,22 +95,27 @@ export default class API {
|
||||
* Retrive a song
|
||||
* @param songId the id to find the song
|
||||
*/
|
||||
static async getSong(songId: number): Promise<Song> {
|
||||
return delay(1).then(() => ({
|
||||
title: "Song",
|
||||
description: "A very very very very very very very very very very very very very very very very very very very very very very very very good song",
|
||||
album: "Album",
|
||||
metrics: {},
|
||||
id: songId
|
||||
}));
|
||||
|
||||
public static async getSong(songId: number): Promise<Song> {
|
||||
return API.fetch({
|
||||
route: `/song/${songId}`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrive an artist
|
||||
*/
|
||||
public static async getArtist(artistId: number): Promise<Artist> {
|
||||
return API.fetch({
|
||||
route: `/artist/${artistId}`
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrive a song's chapters
|
||||
* @param songId the id to find the song
|
||||
*/
|
||||
static async getSongChapters(songId: number): Promise<Chapter[]> {
|
||||
public static async getSongChapters(songId: number): Promise<Chapter[]> {
|
||||
return [1, 2, 3, 4, 5].map((value) => ({
|
||||
start: 100 * (value - 1),
|
||||
end: 100 * value,
|
||||
@@ -89,7 +132,7 @@ export default class API {
|
||||
* Retrieve a song's play history
|
||||
* @param songId the id to find the song
|
||||
*/
|
||||
static async getSongHistory(songId: number): Promise<SongHistory[]> {
|
||||
public static async getSongHistory(songId: number): Promise<SongHistory[]> {
|
||||
return [6, 1, 2, 3, 4, 5].map((value) => ({
|
||||
songId: songId,
|
||||
userId: 1,
|
||||
@@ -101,21 +144,23 @@ export default class API {
|
||||
* Search a song by its name
|
||||
* @param query the string used to find the songs
|
||||
*/
|
||||
static async searchSongs(query: string): Promise<Song[]> {
|
||||
return [{
|
||||
title: "Song",
|
||||
description: "A song",
|
||||
album: "Album",
|
||||
metrics: {},
|
||||
id: 1
|
||||
}];
|
||||
public static async searchSongs(query: string): Promise<Song[]> {
|
||||
return Array.of(4).map((i) => ({
|
||||
id: i,
|
||||
name: `Searched Song ${i}`,
|
||||
artistId: i,
|
||||
genreId: i,
|
||||
albumId: i,
|
||||
cover: dummyIllustration,
|
||||
metrics: {}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a lesson
|
||||
* @param lessonId the id to find the lesson
|
||||
*/
|
||||
static async getLesson(lessonId: number): Promise<Lesson> {
|
||||
public static async getLesson(lessonId: number): Promise<Lesson> {
|
||||
return {
|
||||
title: "Song",
|
||||
description: "A song",
|
||||
@@ -125,43 +170,60 @@ export default class API {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the authenticated user's search history
|
||||
* @param lessonId the id to find the lesson
|
||||
*/
|
||||
public static async getSearchHistory(): Promise<Song[]> {
|
||||
return Array.of(4).map((i) => ({
|
||||
id: i,
|
||||
name: `Song in history ${i}`,
|
||||
artistId: i,
|
||||
genreId: i,
|
||||
albumId: i,
|
||||
cover: dummyIllustration,
|
||||
metrics: {}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the authenticated user's recommendations
|
||||
*/
|
||||
public static async getUserRecommendations(): Promise<Song[]> {
|
||||
return Array.of(4).map((i) => ({
|
||||
id: i,
|
||||
name: `Recommended Song ${i}`,
|
||||
artistId: i,
|
||||
genreId: i,
|
||||
albumId: i,
|
||||
cover: dummyIllustration,
|
||||
metrics: {}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the authenticated user's play history
|
||||
*/
|
||||
public static async getUserPlayHistory(): Promise<Song[]> {
|
||||
return Array.of(4).map((i) => ({
|
||||
id: i,
|
||||
name: `played Song ${i}`,
|
||||
artistId: i,
|
||||
genreId: i,
|
||||
albumId: i,
|
||||
cover: dummyIllustration,
|
||||
metrics: {}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a lesson's history
|
||||
* @param lessonId the id to find the lesson
|
||||
*/
|
||||
static async getLessonHistory(lessonId: number): Promise<LessonHistory[]> {
|
||||
public static async getLessonHistory(lessonId: number): Promise<LessonHistory[]> {
|
||||
return [{
|
||||
lessonId,
|
||||
userId: 1
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the login information status
|
||||
*
|
||||
*/
|
||||
static async checkSigninCredentials(username: string, password: string): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if (username === "katerina" && password === "1234") {
|
||||
return resolve("token signin");
|
||||
}
|
||||
return reject(translate("invalidCredentials"));
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the register information status
|
||||
*/
|
||||
static async checkSignupCredentials(username: string, password: string, email: string): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if (username === "bluub") {
|
||||
return reject(translate("usernameTaken"));
|
||||
}
|
||||
return resolve("token signup");
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ export const publicRoutes = <React.Fragment>
|
||||
</React.Fragment>;
|
||||
|
||||
export const Router = () => {
|
||||
const isAuthentified = useSelector((state) => state.user.token !== undefined)
|
||||
const isAuthentified = useSelector((state) => state.user.accessToken !== undefined)
|
||||
return (
|
||||
<NavigationContainer>
|
||||
<Stack.Navigator>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
module.exports = {
|
||||
"name": "Chromacase",
|
||||
"slug": "Chromacase",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"splash": {
|
||||
"image": "./assets/cover.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"updates": {
|
||||
"fallbackToCacheTimeout": 0
|
||||
},
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"package": "com.chromacase.chromacase",
|
||||
"versionCode": 1
|
||||
},
|
||||
"package": "build.apk"
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/favicon.png"
|
||||
},
|
||||
"extra": {
|
||||
apiUrl: process.env.API_URL,
|
||||
"eas": {
|
||||
"projectId": "dade8e5e-3e2c-49f7-98c5-cf8834c7ebb2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "Chromacase",
|
||||
"slug": "Chromacase",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"updates": {
|
||||
"fallbackToCacheTimeout": 0
|
||||
},
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"package": "com.chromacase.chromacase",
|
||||
"versionCode": 1
|
||||
},
|
||||
"package": "build.apk"
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/favicon.png"
|
||||
},
|
||||
"extra": {
|
||||
"eas": {
|
||||
"projectId": "dade8e5e-3e2c-49f7-98c5-cf8834c7ebb2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
Square,
|
||||
} from "native-base";
|
||||
import React from "react";
|
||||
import { Ionicons, FontAwesome } from "@expo/vector-icons";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
|
||||
export enum SuggestionType {
|
||||
TEXT,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import SearchBar from "../components/SearchBar";
|
||||
import SearchBar, { IllustratedSuggestionProps } from "../components/SearchBar";
|
||||
import { SuggestionList, SuggestionType } from "../components/SearchBar";
|
||||
interface SearchBarSuggestionsProps {
|
||||
onTextSubmit: (text: string) => void;
|
||||
@@ -15,7 +15,7 @@ const filterSuggestions = (text: string, suggestions: SuggestionList) => {
|
||||
case SuggestionType.ILLUSTRATED:
|
||||
return (
|
||||
suggestion.data.text.toLowerCase().includes(text.toLowerCase()) ||
|
||||
suggestion.data.subtext.toLowerCase().includes(text.toLowerCase())
|
||||
(suggestion.data as IllustratedSuggestionProps).subtext.toLowerCase().includes(text.toLowerCase())
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ const SongCard = (props: SongCardProps) => {
|
||||
>
|
||||
<Image
|
||||
style={{ zIndex: 0, aspectRatio: 1, margin: 5, borderRadius: CardBorderRadius}}
|
||||
source={{ uri: "https://i.discogs.com/yHqu3pnLgJq-cVpYNVYu6mE-fbzIrmIRxc6vES5Oi48/rs:fit/g:sm/q:90/h:556/w:600/czM6Ly9kaXNjb2dz/LWRhdGFiYXNlLWlt/YWdlcy9SLTE2NjQ2/ODUwLTE2MDkwNDU5/NzQtNTkxOS5qcGVn.jpeg" }}
|
||||
source={{ uri: albumCover }}
|
||||
alt={[props.songTitle, props.artistName].join('-')}
|
||||
/>
|
||||
<VStack padding={3}>
|
||||
|
||||
@@ -112,7 +112,6 @@ const LoginForm = ({ onSubmit }: SigninFormProps) => {
|
||||
toast.show({ description: resp, colorScheme: 'secondary' })
|
||||
} catch (e) {
|
||||
toast.show({ description: e as string, colorScheme: 'red', avoidKeyboard: true })
|
||||
} finally {
|
||||
setSubmittingForm(false);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -52,7 +52,7 @@ const LoginForm = ({ onSubmit }: SignupFormProps) => {
|
||||
.min(4, translate("passwordTooShort"))
|
||||
.max(100, translate("passwordTooLong"))
|
||||
.matches(
|
||||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])(?=.{8,})/,
|
||||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$-_%\^&\*])(?=.{8,})/,
|
||||
translate(
|
||||
"Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character"
|
||||
)
|
||||
|
||||
@@ -2,9 +2,11 @@ import Metrics from "./Metrics";
|
||||
import Model from "./Model";
|
||||
|
||||
interface Song extends Model {
|
||||
title: string;
|
||||
description: string;
|
||||
album: string;
|
||||
name: string
|
||||
artistId: number | null
|
||||
albumId: number | null
|
||||
genreId: number | null;
|
||||
cover: string;
|
||||
metrics: Metrics;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,11 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
"@testing-library/react-native": "^11.0.0",
|
||||
"@types/node": "^18.11.8",
|
||||
"@types/react": "^18.0.18",
|
||||
"@types/react-native": "^0.69.6",
|
||||
"@types/react-navigation": "^3.4.0",
|
||||
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
|
||||
"react-test-renderer": "17.0.2",
|
||||
"typescript": "~4.3.5"
|
||||
},
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import API from '../API';
|
||||
import { AccessToken } from '../API';
|
||||
|
||||
export const userSlice = createSlice({
|
||||
name: 'user',
|
||||
initialState: {
|
||||
apiAccess: undefined as API | undefined
|
||||
accessToken: undefined as AccessToken | undefined
|
||||
},
|
||||
reducers: {
|
||||
setAPIAccess: (state, action: PayloadAction<API>) => {
|
||||
state.apiAccess = action.payload;
|
||||
setAccessToken: (state, action: PayloadAction<AccessToken>) => {
|
||||
state.accessToken = action.payload;
|
||||
},
|
||||
unsetAPIAccess: (state) => {
|
||||
state.apiAccess = undefined;
|
||||
unsetAccessToken: (state) => {
|
||||
state.accessToken = undefined;
|
||||
},
|
||||
},
|
||||
});
|
||||
export const { setAPIAccess, unsetAPIAccess } = userSlice.actions;
|
||||
export const { setAccessToken, unsetAccessToken } = userSlice.actions;
|
||||
export default userSlice.reducer;
|
||||
+1
-1
@@ -32,7 +32,7 @@
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
|
||||
"types": ["react-native", "jest"], /* Specify type package names to be included without being referenced in a source file. */
|
||||
"types": ["react-native", "jest", "node"], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
"resolveJsonModule": true, /* Enable importing .json files */
|
||||
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
@@ -2,28 +2,28 @@ import React from "react";
|
||||
import { useDispatch } from '../state/Store';
|
||||
import { translate } from "../i18n/i18n";
|
||||
import API from "../API";
|
||||
import { setUserToken } from "../state/UserSlice";
|
||||
import { setAccessToken } from "../state/UserSlice";
|
||||
import { Center, Button, Text } from 'native-base';
|
||||
import SigninForm from "../components/forms/signinform";
|
||||
import SignupForm from "../components/forms/signupform";
|
||||
|
||||
const hanldeSignin = async (username: string, password: string, tokenSetter: (token: string) => void): Promise<string> => {
|
||||
const hanldeSignin = async (username: string, password: string, apiSetter: (accessToken: string) => void): Promise<string> => {
|
||||
try {
|
||||
const response = await API.checkSigninCredentials(username, password);
|
||||
tokenSetter(response);
|
||||
const apiAccess = await API.authenticate({ username, password });
|
||||
apiSetter(apiAccess);
|
||||
return translate("loggedIn");
|
||||
} catch (error) {
|
||||
return error as string;
|
||||
return "Username of password incorrect";
|
||||
}
|
||||
};
|
||||
|
||||
const handleSignup = async (username: string, password: string, email: string, tokenSetter: (t: string) => void): Promise<string> => {
|
||||
const handleSignup = async (username: string, password: string, email: string, apiSetter: (accessToken: string) => void): Promise<string> => {
|
||||
try {
|
||||
const response = await API.checkSignupCredentials(username, password, email);
|
||||
tokenSetter(response);
|
||||
const apiAccess = await API.createAccount({ username, password, email });
|
||||
apiSetter(apiAccess);
|
||||
return translate("loggedIn");
|
||||
} catch (error) {
|
||||
return error as string;
|
||||
return "User already exists";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -33,14 +33,10 @@ const AuthenticationView = () => {
|
||||
|
||||
return (
|
||||
<Center style={{ flex: 1 }}>
|
||||
<Text>{translate('welcome')}</Text>
|
||||
{mode === "signin" ? (<>
|
||||
<Text fontWeight='thin'>username, password:</Text>
|
||||
<Text fontWeight='thin'>katerina, 1234</Text>
|
||||
<SigninForm onSubmit={(username, password) => hanldeSignin(username, password, (token) => dispatch(setUserToken(token)))} />
|
||||
</>) : (
|
||||
<SignupForm onSubmit={(username, password, email) => handleSignup(username, password, email, (token) => dispatch(setUserToken(token)))} />
|
||||
)}
|
||||
{mode === "signin"
|
||||
? <SigninForm onSubmit={(username, password) => hanldeSignin(username, password, (accessToken) => dispatch(setAccessToken(accessToken)))} />
|
||||
: <SignupForm onSubmit={(username, password, email) => handleSignup(username, password, email, (accessToken) => dispatch(setAccessToken(accessToken)))} />
|
||||
}
|
||||
{ mode ==="signin" && <Button variant="outline" marginTop={5} colorScheme="error" >{translate("forgottenPassword")}</Button> }
|
||||
<Button variant='outline' marginTop={5} colorScheme='primary' onPress={() => setMode(mode === "signin" ? "signup" : "signin")}>
|
||||
<Text>{translate(mode === "signin" ? "signUp" : "signIn")}</Text>
|
||||
|
||||
+36
-32
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useQueries, useQuery } from "react-query";
|
||||
import API from "../API";
|
||||
import LoadingComponent from "../components/Loading";
|
||||
import { Box, ScrollView, Flex, useBreakpointValue, Text, VStack, Progress, Button, useTheme, Heading, Divider } from 'native-base';
|
||||
import { Box, ScrollView, Flex, useBreakpointValue, Text, VStack, Progress, Button, useTheme, Heading } from 'native-base';
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import SongCardGrid from '../components/SongCardGrid';
|
||||
import CompetenciesTable from '../components/CompetenciesTable'
|
||||
@@ -31,15 +31,20 @@ const HomeView = () => {
|
||||
const screenSize = useBreakpointValue({ base: 'small', md: "big"});
|
||||
const flexDirection = useBreakpointValue({ base: 'column', xl: "row"});
|
||||
const userQuery = useQuery(['user'], () => API.getUserInfo());
|
||||
|
||||
if (!userQuery.data) {
|
||||
const playHistoryQuery = useQuery(['history', 'play'], () => API.getUserPlayHistory());
|
||||
const searchHistoryQuery = useQuery(['history', 'search'], () => API.getSearchHistory());
|
||||
const skillsQuery = useQuery(['skills'], () => API.getUserSkills());
|
||||
const nextStepQuery = useQuery(['user', 'recommendations'], () => API.getUserRecommendations());
|
||||
const artistsQueries = useQueries((playHistoryQuery.data?.concat(searchHistoryQuery.data ?? []).concat(nextStepQuery.data ?? []) ?? []).map((song) => (
|
||||
{ queryKey: ['artist', song.id], queryFn: () => API.getArtist(song.id) }
|
||||
)));
|
||||
if (!userQuery.data || !skillsQuery.data || !searchHistoryQuery.data || !playHistoryQuery.data) {
|
||||
return <Box style={{ flexGrow: 1, justifyContent: 'center' }}>
|
||||
<LoadingComponent/>
|
||||
</Box>
|
||||
}
|
||||
return <ScrollView>
|
||||
<Box style={{ display: 'flex', padding: 30 }}>
|
||||
|
||||
<Box textAlign={ screenSize == 'small' ? 'center' : undefined } style={{ flexDirection, justifyContent: 'center', display: 'flex' }}>
|
||||
<Text fontSize="xl" flex={screenSize == 'small' ? 1 : 2}>{`${translate('welcome')} ${userQuery.data.name}!`} </Text>
|
||||
<Box flex={1}>
|
||||
@@ -51,26 +56,21 @@ const HomeView = () => {
|
||||
<Box flex={2}>
|
||||
<SongCardGrid
|
||||
heading={translate('goNextStep')}
|
||||
songs={[ ...Array(4).keys() ].map(() => ({
|
||||
albumCover: "",
|
||||
songTitle: "Song",
|
||||
artistName: "Artist",
|
||||
songId: 1
|
||||
}))}
|
||||
songs={nextStepQuery.data?.filter((song) => artistsQueries.find((artistQuery) => artistQuery.data?.id === song.artistId))
|
||||
.map((song) => ({
|
||||
albumCover: song.cover,
|
||||
songTitle: song.name,
|
||||
songId: song.id,
|
||||
artistName: artistsQueries.find((artistQuery) => artistQuery.data?.id === song.artistId)!.data!.name
|
||||
})) ?? []
|
||||
}
|
||||
/>
|
||||
|
||||
<Flex style={{ flexDirection }}>
|
||||
<Box flex={1} paddingY={5}>
|
||||
<Heading>{translate('mySkillsToImprove')}</Heading>
|
||||
<Box padding={5}>
|
||||
<CompetenciesTable
|
||||
pedalsCompetency= {Math.random() * 100}
|
||||
rightHandCompetency={Math.random() * 100}
|
||||
leftHandCompetency= {Math.random() * 100}
|
||||
accuracyCompetency= {Math.random() * 100}
|
||||
arpegeCompetency= {Math.random() * 100}
|
||||
chordsCompetency= {Math.random() * 100}
|
||||
/>
|
||||
<CompetenciesTable {...skillsQuery.data}/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -78,12 +78,14 @@ const HomeView = () => {
|
||||
<SongCardGrid
|
||||
heading={translate('recentlyPlayed')}
|
||||
maxItemPerRow={2}
|
||||
songs={[ ...Array(4).keys() ].map(() => ({
|
||||
albumCover: "",
|
||||
songTitle: "Song",
|
||||
artistName: "Artist",
|
||||
songId: 1
|
||||
}))}
|
||||
songs={playHistoryQuery.data?.filter((song) => artistsQueries.find((artistQuery) => artistQuery.data?.id === song.artistId))
|
||||
.map((song) => ({
|
||||
albumCover: song.cover,
|
||||
songTitle: song.name,
|
||||
songId: song.id,
|
||||
artistName: artistsQueries.find((artistQuery) => artistQuery.data?.id === song.artistId)!.data!.name
|
||||
})) ?? []
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
@@ -99,12 +101,14 @@ const HomeView = () => {
|
||||
<SongCardGrid
|
||||
maxItemPerRow={2}
|
||||
heading={translate('lastSearched')}
|
||||
songs={[ ...Array(4).keys() ].map(() => ({
|
||||
albumCover: "",
|
||||
songTitle: "Song",
|
||||
artistName: "Artist",
|
||||
songId: 1
|
||||
}))}
|
||||
songs={searchHistoryQuery.data?.filter((song) => artistsQueries.find((artistQuery) => artistQuery.data?.id === song.artistId))
|
||||
.map((song) => ({
|
||||
albumCover: song.cover,
|
||||
songTitle: song.name,
|
||||
songId: song.id,
|
||||
artistName: artistsQueries.find((artistQuery) => artistQuery.data?.id === song.artistId)!.data!.name
|
||||
})) ?? []
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
+24
-51
@@ -1,63 +1,36 @@
|
||||
import React from "react";
|
||||
import { useDispatch } from "../state/Store";
|
||||
import { translate } from "../i18n/i18n";
|
||||
import { Box, Button } from "native-base";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Box } from "native-base";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import SearchBarSuggestions from "../components/SearchBarSuggestions";
|
||||
import {
|
||||
SuggestionList,
|
||||
SuggestionType,
|
||||
IllustratedSuggestionProps,
|
||||
} from "../components/SearchBar";
|
||||
|
||||
const onTextSubmit = (text: string) => {
|
||||
console.log(text);
|
||||
};
|
||||
import { useQueries, useQuery } from "react-query";
|
||||
import { SuggestionType } from "../components/SearchBar";
|
||||
import API from "../API";
|
||||
|
||||
const SearchView = () => {
|
||||
const [query, setQuery] = useState<string>();
|
||||
const navigation = useNavigation();
|
||||
|
||||
const IllustratedSuggestion: IllustratedSuggestionProps = {
|
||||
text: "Love Story",
|
||||
subtext: "Taylor Swift",
|
||||
imageSrc:
|
||||
"https://i.discogs.com/yHqu3pnLgJq-cVpYNVYu6mE-fbzIrmIRxc6vES5Oi48/rs:fit/g:sm/q:90/h:556/w:600/czM6Ly9kaXNjb2dz/LWRhdGFiYXNlLWlt/YWdlcy9SLTE2NjQ2/ODUwLTE2MDkwNDU5/NzQtNTkxOS5qcGVn.jpeg",
|
||||
onPress: () => navigation.navigate("Song", { songId: 1 }),
|
||||
};
|
||||
// fill the suggestions with the data from the backend
|
||||
const suggestions: SuggestionList = [
|
||||
{
|
||||
type: SuggestionType.ILLUSTRATED,
|
||||
data: IllustratedSuggestion,
|
||||
},
|
||||
{
|
||||
type: SuggestionType.ILLUSTRATED,
|
||||
data: IllustratedSuggestion,
|
||||
},
|
||||
{
|
||||
type: SuggestionType.ILLUSTRATED,
|
||||
data: {
|
||||
text: "Shed a Light",
|
||||
subtext: "Robin Schulz & David Guetta",
|
||||
imageSrc:
|
||||
"https://imgs.search.brave.com/O9j2Z-oWiniq3lj7d-dAOgXLWCIqnHaFegmaSeIkWOY/rs:fit:560:320:1/g:ce/aHR0cHM6Ly91cGxv/YWQud2lraW1lZGlh/Lm9yZy93aWtpcGVk/aWEvZW4vdGh1bWIv/OC84ZS9TaGVkX2Ff/TGlnaHRfUm9iaW5f/U2NodWx6LmpwZy81/MTJweC1TaGVkX2Ff/TGlnaHRfUm9iaW5f/U2NodWx6LmpwZw",
|
||||
onPress: () => navigation.navigate("Song", { songId: 1 }),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: SuggestionType.TEXT,
|
||||
data: {
|
||||
text: "Lady Gaga",
|
||||
onPress: () => navigation.navigate("Song", { songId: 1 }),
|
||||
},
|
||||
},
|
||||
];
|
||||
const searchQuery = useQuery(
|
||||
['search', query],
|
||||
() => API.searchSongs(query!),
|
||||
{ enabled: query != undefined }
|
||||
);
|
||||
const artistsQueries = useQueries(searchQuery.data?.map((song) => (
|
||||
{ queryKey: ['artist', song.id], queryFn: () => API.getArtist(song.id) }
|
||||
)) ??[]);
|
||||
|
||||
return (
|
||||
<Box style={{ padding: 10 }}>
|
||||
<SearchBarSuggestions
|
||||
onTextSubmit={onTextSubmit}
|
||||
suggestions={suggestions}
|
||||
onTextSubmit={setQuery}
|
||||
suggestions={searchQuery.data?.map((searchResult) => ({
|
||||
type: SuggestionType.ILLUSTRATED,
|
||||
data: {
|
||||
text: searchResult.name,
|
||||
subtext: artistsQueries.find((artistQuery) => artistQuery.data?.id == searchResult.artistId)?.data?.name ?? "",
|
||||
imageSrc: searchResult.cover,
|
||||
onPress: () => navigation.navigate("Song", { songId: searchResult.id })
|
||||
}
|
||||
})) ?? []}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -2,8 +2,9 @@ import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { Center, Button, Text, Switch, Slider, Select, Heading } from "native-base";
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { unsetUserToken } from '../state/UserSlice';
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { unsetAPIAccess } from '../state/UserSlice';
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useSelector } from '../state/Store';
|
||||
import { useLanguage } from "../state/LanguageSlice";
|
||||
import i18n, { AvailableLanguages, DefaultLanguage, translate } from "../i18n/i18n";
|
||||
|
||||
@@ -38,7 +39,7 @@ const MainView = ({navigation}) => {
|
||||
{ translate('googleacctBtn')}
|
||||
</Button>
|
||||
|
||||
<Button variant='ghost' onPress={() => dispatch(unsetUserToken())} >
|
||||
<Button variant='ghost' onPress={() => dispatch(unsetAPIAccess())} >
|
||||
{ translate('signoutBtn')}
|
||||
</Button>
|
||||
</Center>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useRoute } from "@react-navigation/native";
|
||||
import { Button, Divider, Box, Center, Image, Text, VStack, PresenceTransition, Icon } from "native-base";
|
||||
import API from "../API";
|
||||
import { useQuery } from 'react-query';
|
||||
import LoadingComponent from "../components/Loading";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import logo from '../assets/cover.png';
|
||||
import { translate } from "../i18n/i18n";
|
||||
import formatDuration from "format-duration";
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useSelector } from "../state/Store";
|
||||
import API from "../API";
|
||||
|
||||
interface SongLobbyProps {
|
||||
// The unique identifier to find a song
|
||||
@@ -34,12 +34,12 @@ const SongLobbyView = () => {
|
||||
<Box style={{ padding: 30, flexDirection: 'column' }}>
|
||||
<Box style={{ flexDirection: 'row', height: '30%'}}>
|
||||
<Box style={{ flex: 3 }}>
|
||||
<Image source={logo} style={{ height: '100%', width: undefined, resizeMode: 'contain' }}/>
|
||||
<Image source={{ uri: songQuery.data!.cover }} style={{ height: '100%', width: undefined, resizeMode: 'contain' }}/>
|
||||
</Box>
|
||||
<Box style={{ flex: 0.5 }}/>
|
||||
<Box style={{ flex: 3, padding: 10, flexDirection: 'column', justifyContent: 'space-between' }}>
|
||||
<Box flex={1}>
|
||||
<Text bold fontSize='lg'>{songQuery.data!.title}</Text>
|
||||
<Text bold fontSize='lg'>{songQuery.data!.name}</Text>
|
||||
<Text>{'3:20'} - {translate('level')} { chaptersQuery.data!.reduce((a, b) => a + b.difficulty, 0) / chaptersQuery.data!.length }</Text>
|
||||
<Button width='auto' rightIcon={<Icon as={Ionicons} name="play-outline"/>}>{ translate('playBtn') }</Button>
|
||||
</Box>
|
||||
@@ -55,7 +55,7 @@ const SongLobbyView = () => {
|
||||
<Text>{scoresQuery.data!.slice(-1)[0]!.score}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Text style={{ paddingBottom: 10 }}>{songQuery.data!.description}</Text>
|
||||
{/* <Text style={{ paddingBottom: 10 }}>{songQuery.data!.description}</Text> */}
|
||||
<Box flexDirection='row'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
|
||||
@@ -2405,6 +2405,16 @@
|
||||
resolved "https://registry.yarnpkg.com/@react-native/polyfills/-/polyfills-2.0.0.tgz#4c40b74655c83982c8cf47530ee7dc13d957b6aa"
|
||||
integrity sha512-K0aGNn1TjalKj+65D7ycc1//H9roAQ51GJVk5ZJQFb2teECGmzd86bYDC0aYdbRf7gtovescq4Zt6FR0tgXiHQ==
|
||||
|
||||
"@react-navigation/core@^3.7.9":
|
||||
version "3.7.9"
|
||||
resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-3.7.9.tgz#3f7ba0fcb6c8d74a77a057382af198d84c7c4e3b"
|
||||
integrity sha512-EknbzM8OI9A5alRxXtQRV5Awle68B+z1QAxNty5DxmlS3BNfmduWNGnim159ROyqxkuDffK9L/U/Tbd45mx+Jg==
|
||||
dependencies:
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
path-to-regexp "^1.8.0"
|
||||
query-string "^6.13.6"
|
||||
react-is "^16.13.0"
|
||||
|
||||
"@react-navigation/core@^6.4.0":
|
||||
version "6.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-6.4.0.tgz#c44d33a8d8ef010a102c7f831fc8add772678509"
|
||||
@@ -2430,6 +2440,14 @@
|
||||
"@react-navigation/elements" "^1.3.6"
|
||||
warn-once "^0.1.0"
|
||||
|
||||
"@react-navigation/native@^3.8.4":
|
||||
version "3.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-3.8.4.tgz#4d77f86506364ecf18b33c7f8740afb6763d0b37"
|
||||
integrity sha512-gXSVcL7bfFDyVkvyg1FiAqTCIgZub5K1X/TZqURBs2CPqDpfX1OsCtB9D33eTF14SpbfgHW866btqrrxoCACfg==
|
||||
dependencies:
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
react-native-safe-area-view "^0.14.9"
|
||||
|
||||
"@react-navigation/native@^6.0.11":
|
||||
version "6.0.13"
|
||||
resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-6.0.13.tgz#ec504120e193ea6a7f24ffa765a1338be5a3160a"
|
||||
@@ -2976,6 +2994,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.8.2.tgz#17d42c6322d917764dd3d2d3a10d7884925de067"
|
||||
integrity sha512-cRMwIgdDN43GO4xMWAfJAecYn8wV4JbsOGHNfNUIDiuYkUYAR5ec4Rj7IO2SAhFPEfpPtLtUTbbny/TCT7aDwA==
|
||||
|
||||
"@types/node@^18.11.8":
|
||||
version "18.11.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.8.tgz#16d222a58d4363a2a359656dd20b28414de5d265"
|
||||
integrity sha512-uGwPWlE0Hj972KkHtCDVwZ8O39GmyjfMane1Z3GUBGGnkZ2USDq7SxLpVIiIHpweY9DS0QTDH0Nw7RNBsAAZ5A==
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
|
||||
@@ -3005,6 +3028,13 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-navigation@^3.4.0":
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-navigation/-/react-navigation-3.4.0.tgz#d610d13c9162312079a8ca102660143f07432cbf"
|
||||
integrity sha512-Y7F5zU8BTBK8tEOvUqgvwvPZ7+9vnc2UI1vHwJ/9ZJG98TntNv04GWa6lrn4MA4149pqw6cyNw/V49Yd2osAFQ==
|
||||
dependencies:
|
||||
react-navigation "*"
|
||||
|
||||
"@types/react-query@^1.2.9":
|
||||
version "1.2.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-query/-/react-query-1.2.9.tgz#61df5a0594ea4b90234f9c0fdd8f12a06e331fed"
|
||||
@@ -3440,6 +3470,11 @@ babel-plugin-syntax-trailing-function-commas@^7.0.0-beta.0:
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz#aa213c1435e2bffeb6fca842287ef534ad05d5cf"
|
||||
integrity sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==
|
||||
|
||||
babel-plugin-transform-inline-environment-variables@^0.4.4:
|
||||
version "0.4.4"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-inline-environment-variables/-/babel-plugin-transform-inline-environment-variables-0.4.4.tgz#974245008b3cbbd646bd81707af147aea3acca43"
|
||||
integrity sha512-bJILBtn5a11SmtR2j/3mBOjX4K3weC6cq+NNZ7hG22wCAqpc3qtj/iN7dSe9HDiS46lgp1nHsQgeYrea/RUe+g==
|
||||
|
||||
babel-preset-current-node-syntax@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b"
|
||||
@@ -5358,6 +5393,11 @@ hermes-profile-transformer@^0.0.6:
|
||||
dependencies:
|
||||
source-map "^0.7.3"
|
||||
|
||||
hoist-non-react-statics@^2.3.1:
|
||||
version "2.5.5"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
|
||||
integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
|
||||
|
||||
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
@@ -5811,6 +5851,11 @@ is-wsl@^2.2.0:
|
||||
dependencies:
|
||||
is-docker "^2.0.0"
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
|
||||
|
||||
isarray@1.0.0, isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
@@ -7859,6 +7904,13 @@ path-parse@^1.0.5, path-parse@^1.0.7:
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
path-to-regexp@^1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
|
||||
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
|
||||
dependencies:
|
||||
isarray "0.0.1"
|
||||
|
||||
path-type@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||
@@ -8050,6 +8102,16 @@ qs@6.7.0:
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||
|
||||
query-string@^6.13.6:
|
||||
version "6.14.1"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a"
|
||||
integrity sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==
|
||||
dependencies:
|
||||
decode-uri-component "^0.2.0"
|
||||
filter-obj "^1.1.0"
|
||||
split-on-first "^1.0.0"
|
||||
strict-uri-encode "^2.0.0"
|
||||
|
||||
query-string@^7.0.0:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.1.tgz#754620669db978625a90f635f12617c271a088e1"
|
||||
@@ -8182,6 +8244,13 @@ react-native-safe-area-context@4.2.4:
|
||||
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.2.4.tgz#4df42819759c4d3c74252c8678c2772cfa2271a6"
|
||||
integrity sha512-OOX+W2G4YYufvryonn6Kw6YnyT8ZThkxPHZBD04NLHaZmicUaaDVII/PZ3M5fD1o5N62+T+8K4bCS5Un2ggvkA==
|
||||
|
||||
react-native-safe-area-view@^0.14.9:
|
||||
version "0.14.9"
|
||||
resolved "https://registry.yarnpkg.com/react-native-safe-area-view/-/react-native-safe-area-view-0.14.9.tgz#90ee8383037010d9a5055a97cf97e4c1da1f0c3d"
|
||||
integrity sha512-WII/ulhpVyL/qbYb7vydq7dJAfZRBcEhg4/UWt6F6nAKpLa3gAceMOxBxI914ppwSP/TdUsandFy6lkJQE0z4A==
|
||||
dependencies:
|
||||
hoist-non-react-statics "^2.3.1"
|
||||
|
||||
react-native-screens@~3.11.1:
|
||||
version "3.11.1"
|
||||
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-3.11.1.tgz#9bca9968986ca9195cb1e7e6fca37543bde64ecb"
|
||||
@@ -8263,6 +8332,14 @@ react-native@0.68.2:
|
||||
whatwg-fetch "^3.0.0"
|
||||
ws "^6.1.4"
|
||||
|
||||
react-navigation@*:
|
||||
version "4.4.4"
|
||||
resolved "https://registry.yarnpkg.com/react-navigation/-/react-navigation-4.4.4.tgz#8cda2219196311db440e54998bc724523359949f"
|
||||
integrity sha512-08Nzy1aKEd73496CsuzN49vLFmxPKYF5WpKGgGvkQ10clB79IRM2BtAfVl6NgPKuUM8FXq1wCsrjo/c5ftl5og==
|
||||
dependencies:
|
||||
"@react-navigation/core" "^3.7.9"
|
||||
"@react-navigation/native" "^3.8.4"
|
||||
|
||||
react-query@*:
|
||||
version "3.39.2"
|
||||
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.2.tgz#9224140f0296f01e9664b78ed6e4f69a0cc9216f"
|
||||
|
||||
Reference in New Issue
Block a user