diff --git a/functions/Stores.ts b/functions/Stores.ts deleted file mode 100644 index 6a38766..0000000 --- a/functions/Stores.ts +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { MobXProviderContext } from 'mobx-react'; -import * as stores from 'app/skyhitz-common'; -type Stores = typeof stores; -import 'mobx-react-lite/batchingForReactDom'; - -export function Stores() { - return React.useContext(MobXProviderContext) as Stores; -} diff --git a/interfaces/Interfaces.tsx b/interfaces/Interfaces.tsx deleted file mode 100644 index a56f625..0000000 --- a/interfaces/Interfaces.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export interface NavStatelessComponent extends React.StatelessComponent { - navigationOptions?: Object; - router?: any; -} diff --git a/modules/accounts/WalletConnectBtn.tsx b/modules/accounts/WalletConnectBtn.tsx deleted file mode 100644 index 034c75a..0000000 --- a/modules/accounts/WalletConnectBtn.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { useEffect } from 'react'; -import { Pressable, Text } from 'react-native'; -import WalletConnectIcon from 'app/modules/ui/icons/walletconnect-icon'; -import { Stores } from 'app/functions/Stores'; -import QRCodeModal from '@walletconnect/qrcode-modal'; -import { observer } from 'mobx-react'; -import tw from 'twin.macro'; -import { signManageDataOp } from 'app/stellar'; - -export default observer(({ signInWithXDR }: { signInWithXDR?: (_) => {} }) => { - let { walletConnectStore } = Stores(); - - useEffect(() => { - if (!walletConnectStore.uri) return QRCodeModal.close(); - QRCodeModal.open(walletConnectStore.uri, () => {}, { - desktopLinks: [], - mobileLinks: ['lobstr'], - }); - }, [walletConnectStore.uri]); - - const handleSignInWithXdr = async (publicKey: string) => { - const xdr = await signManageDataOp(publicKey); - const { signedXDR } = await walletConnectStore.signXdr(xdr); - signInWithXDR && signInWithXDR(signedXDR); - }; - - useEffect(() => { - if (walletConnectStore.publicKey && signInWithXDR) - handleSignInWithXdr(walletConnectStore.publicKey); - }, [walletConnectStore.publicKey]); - - return ( - walletConnectStore.connect()} - > - - {walletConnectStore.state === 'session-proposal' - ? 'Waiting for approval' - : walletConnectStore.state === 'session-created' - ? 'Connected' - : 'WalletConnect'} - - - - ); -}); diff --git a/modules/navigation/RootNavigation.tsx b/modules/navigation/RootNavigation.tsx deleted file mode 100644 index be35de4..0000000 --- a/modules/navigation/RootNavigation.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { useState, useEffect, lazy } from 'react'; -import { StatusBar } from 'react-native'; -import { observer } from 'mobx-react'; -import { useMediaQuery } from 'react-responsive'; -import { Stores } from 'app/functions/Stores'; -import LoadingScreen from 'app/modules/accounts/LoadingScreen'; -import { SuspenseLoading } from './SuspenseLoading'; - -const LazyAppStackNavigator = lazy(() => - import('app/modules/navigation/LazyAppStackNavigator').then((mod) => ({ - default: mod.LazyAppStackNavigator, - })) -); - -export const LazyAppStackNavigatorSuspense = (props) => ( - - - -); - -const LazyNavigationContainer = lazy(() => - import('app/modules/navigation/LazyNavigationContainer') -); - -const LazyNavigationContainerSuspense = (props) => ( - - - -); - -export default observer(() => { - const [loaded, setLoaded] = useState(false); - const { sessionStore } = Stores(); - - StatusBar.setBarStyle('light-content'); - - const loadAll = async () => { - await sessionStore.loadSession(); - setLoaded(true); - }; - - useEffect(() => { - loadAll(); - }, []); - - const [headerShown, setHeaderShown] = useState(true); - const isDesktop = useMediaQuery({ minWidth: 768 }); - useEffect(() => { - if (isDesktop) { - setHeaderShown(false); - } - }, []); - - if (loaded) { - return ( - - - - ); - } - return ; -}); diff --git a/modules/player/player-screen/forward-btn/ForwardBtn.tsx b/modules/player/player-screen/forward-btn/ForwardBtn.tsx deleted file mode 100644 index 9776e5b..0000000 --- a/modules/player/player-screen/forward-btn/ForwardBtn.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { StyleSheet, Pressable } from 'react-native'; -import { inject } from 'mobx-react'; -import * as stores from 'app/skyhitz-common'; -import SkipForwardIcon from 'app/modules/ui/icons/skip-forward'; -import cursorPointer from 'app/constants/CursorPointer'; -import Colors from 'app/constants/Colors'; -type Stores = typeof stores; - -const ForwardBtn = inject((stores: Stores) => ({ - playNext: stores.playerStore.playNext.bind(stores.playerStore), -}))(({ playNext, size = 24 }: any) => ( - playNext()} - > - - -)); - -export default ForwardBtn; - -var styles = StyleSheet.create({ - controlTouch: { - alignSelf: 'center', - }, - forwardBtn: { - width: 24, - height: 18, - }, -}); diff --git a/modules/player/player-screen/like-btn/LikeBtn.tsx b/modules/player/player-screen/like-btn/LikeBtn.tsx deleted file mode 100644 index ac9dff3..0000000 --- a/modules/player/player-screen/like-btn/LikeBtn.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import { StyleSheet, Pressable } from 'react-native'; -import { inject } from 'mobx-react'; -import LikeIcon from 'app/modules/ui/icons/like'; -import Colors from 'app/constants/Colors'; -import * as stores from 'app/skyhitz-common'; -import cursorPointer from 'app/constants/CursorPointer'; -type Stores = typeof stores; - -@inject((stores: Stores) => ({ - toggleLike: stores.likesStore.toggleLike.bind(stores.likesStore), - isLiked: stores.likesStore.isLiked, - entry: stores.playerStore.entry, -})) -export default class LikeBtn extends React.Component { - render() { - if (!this.props.entry) { - return null; - } - if (this.props.isLiked) { - return ( - this.props.toggleLike(this.props.entry)} - > - - - ); - } - return ( - this.props.toggleLike(this.props.entry)} - > - - - ); - } -} - -var styles = StyleSheet.create({ - controlTouch: { - width: 32, - height: 28, - }, -}); diff --git a/modules/player/player-screen/likers-section/LikersSection.tsx b/modules/player/player-screen/likers-section/LikersSection.tsx deleted file mode 100644 index c41b5fc..0000000 --- a/modules/player/player-screen/likers-section/LikersSection.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React from 'react'; -import { inject } from 'mobx-react'; -import { StyleSheet, View, Text } from 'react-native'; -import LikeBtn from 'app/modules/player/player-screen/like-btn/LikeBtn'; -import Divider from 'app/modules/ui/Divider'; -import { UserAvatar } from 'app/modules/ui/UserAvatar'; -import * as L from 'list'; -import * as stores from 'app/skyhitz-common'; -type Stores = typeof stores; - -@inject((stores: Stores) => ({ - likers: stores.likesStore.entryLikes, - hasMoreLikers: stores.likesStore.hasMoreLikers, - plusLikers: stores.likesStore.plusLikers, -})) -export default class LikersSection extends React.Component { - renderMoreLikersBtn() { - if (!this.props.hasMoreLikers) { - return null; - } - return ( - - +{this.props.plusLikers} - - ); - } - - render() { - return ( - - - Liked By - - - - - - - {this.props.likers && - L.map( - (liker: any) => ( - - - - ), - this.props.likers - )} - {this.renderMoreLikersBtn()} - - - ); - } -} - -let styles = StyleSheet.create({ - bottomSection: { - width: '100%', - paddingLeft: 15, - paddingRight: 15, - flexDirection: 'column', - justifyContent: 'flex-end', - maxWidth: 650, - alignSelf: 'center', - }, - likedByWrap: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - }, - actionsWrap: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - }, - likedByText: { - height: 30, - lineHeight: 30, - fontSize: 14, - color: 'white', - textAlign: 'left', - width: 100, - }, - likers: { - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'flex-start', - flexWrap: 'nowrap', - marginTop: 10, - marginBottom: 15, - minHeight: 30, - }, - liker: { - marginRight: 7, - }, - profilepic: { - borderRadius: 15, - width: 30, - height: 30, - }, - plusFriendsCircle: { - borderRadius: 15, - width: 30, - height: 30, - backgroundColor: '#9a9999', - }, - plusFriends: { - color: 'white', - fontSize: 13, - textAlign: 'center', - backgroundColor: 'transparent', - flex: 1, - paddingTop: 8, - }, -}); diff --git a/modules/player/player-screen/prev-btn/PrevBtn.tsx b/modules/player/player-screen/prev-btn/PrevBtn.tsx deleted file mode 100644 index 52c7c6d..0000000 --- a/modules/player/player-screen/prev-btn/PrevBtn.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { StyleSheet, Pressable } from 'react-native'; -import { inject } from 'mobx-react'; -import * as stores from 'app/skyhitz-common'; -import SkipBackwardIcon from 'app/modules/ui/icons/skip-backward'; -import cursorPointer from 'app/constants/CursorPointer'; - -type Stores = typeof stores; - -const PrevBtn = inject((stores: Stores) => ({ - playPrev: stores.playerStore.playPrev.bind(stores.playerStore), -}))(({ playPrev, size = 24 }: any) => ( - playPrev()} - > - - -)); - -export default PrevBtn; - -var styles = StyleSheet.create({ - controlTouch: { - alignSelf: 'center', - }, - prevBtn: { - width: 24, - height: 18, - }, -}); diff --git a/modules/player/player-screen/video-player/FullscreenControl.tsx b/modules/player/player-screen/video-player/FullscreenControl.tsx deleted file mode 100644 index 00b39de..0000000 --- a/modules/player/player-screen/video-player/FullscreenControl.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { inject } from 'mobx-react'; -import Control from 'app/modules/player/player-screen/video-player/Control'; -import { - FullscreenEnterIcon, - FullscreenExitIcon, -} from 'app/modules/player/player-screen/video-player/VideoIcons'; -import * as stores from 'app/skyhitz-common'; -import cursorPointer from 'app/constants/CursorPointer'; -type Stores = typeof stores; - -const FullscreenControl = inject((stores: Stores) => ({ - isOnFullScreenMode: stores.playerStore.isOnFullScreenMode, - presentFullscreenPlayer: stores.playerStore.presentFullscreenPlayer.bind( - stores.playerStore - ), - dismissFullscreenPlayer: stores.playerStore.dismissFullscreenPlayer.bind( - stores.playerStore - ), -}))( - ({ - isOnFullScreenMode, - presentFullscreenPlayer, - dismissFullscreenPlayer, - }: any) => ( - { - isOnFullScreenMode - ? dismissFullscreenPlayer() - : presentFullscreenPlayer(); - }} - > - {isOnFullScreenMode ? : } - - ) -); - -export default FullscreenControl; diff --git a/modules/player/player-screen/video-player/VideoTimeDisplay.tsx b/modules/player/player-screen/video-player/VideoTimeDisplay.tsx deleted file mode 100644 index 6276608..0000000 --- a/modules/player/player-screen/video-player/VideoTimeDisplay.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import { Text, StyleSheet } from 'react-native'; -import { observer } from 'mobx-react'; -import { PLAYBACK_STATES } from 'app/modules/player/player-screen/video-player/UiStates'; -import { Stores } from 'app/functions/Stores'; - -export const CurrentTimeDisplay = observer(() => { - const { playerStore } = Stores(); - if (playerStore.playbackState !== PLAYBACK_STATES.LOADING) { - return ( - {playerStore.positionDisplay} - ); - } - return {'00:00'}; -}); - -export const DurationDisplay = observer(() => { - const { playerStore } = Stores(); - if (playerStore.playbackState !== PLAYBACK_STATES.LOADING) { - return {playerStore.durationDisplay}; - } - return {'00:00'}; -}); - -let styles = StyleSheet.create({ - currentTimeText: { - color: '#FFFFFF', - fontSize: 10, - backgroundColor: 'transparent', - marginRight: 10, - width: 30, - textAlign: 'center', - }, - text: { - color: '#FFFFFF', - fontSize: 10, - backgroundColor: 'transparent', - marginLeft: 10, - width: 30, - textAlign: 'center', - }, -}); diff --git a/modules/playlists/CollectionScreen.tsx b/modules/playlists/CollectionScreen.tsx deleted file mode 100644 index 51a7d71..0000000 --- a/modules/playlists/CollectionScreen.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { ScrollView } from 'react-native'; -import { inject } from 'mobx-react'; -import EntryRow from 'app/modules/ui/EntryRow'; -import SearchingLoader from 'app/modules/ui/SearchingLoader'; -import Colors from 'app/constants/Colors'; -import BottomPlaceholder from 'app/modules/ui/BottomPlaceholder'; -import * as stores from 'app/skyhitz-common'; -import * as L from 'list'; -import ResponsiveLayout from '../ui/ResponsiveLayout'; -type Stores = typeof stores; - -@inject((stores: Stores) => ({ - loadAndPlay: stores.playerStore.loadAndPlay.bind(stores.playerStore), - entries: stores.userEntriesStore.entries, - loading: stores.userEntriesStore.loading, -})) -export default class CollectionScreen extends React.Component { - render() { - return ( - - - {SearchingLoader(this.props.loading)} - {L.map( - (entry: any) => ( - - ), - this.props.entries - )} - - - - ); - } -} diff --git a/modules/playlists/LikesScreen.tsx b/modules/playlists/LikesScreen.tsx deleted file mode 100644 index 88e3a24..0000000 --- a/modules/playlists/LikesScreen.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { ScrollView } from 'react-native'; -import { inject } from 'mobx-react'; -import EntryRow from 'app/modules/ui/EntryRow'; -import SearchingLoader from 'app/modules/ui/SearchingLoader'; -import Colors from 'app/constants/Colors'; -import BottomPlaceholder from 'app/modules/ui/BottomPlaceholder'; -import * as stores from 'app/skyhitz-common'; -import * as L from 'list'; -import ResponsiveLayout from '../ui/ResponsiveLayout'; -type Stores = typeof stores; - -@inject((stores: Stores) => ({ - loadAndPlay: stores.playerStore.loadAndPlay.bind(stores.playerStore), - entries: stores.likesStore.userLikes, - loading: stores.likesStore.loading, -})) -export default class LikesScreen extends React.Component { - render() { - return ( - - - {SearchingLoader(this.props.loading)} - {L.map( - (entry: any) => ( - - ), - this.props.entries - )} - - - - ); - } -} diff --git a/modules/profile/EditProfilePhotoBtn.tsx b/modules/profile/EditProfilePhotoBtn.tsx deleted file mode 100644 index e4f2979..0000000 --- a/modules/profile/EditProfilePhotoBtn.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { Pressable, StyleSheet, Text, Platform } from 'react-native'; -import * as ImagePicker from 'expo-image-picker'; -import * as Permissions from 'expo-permissions'; -import { inject } from 'mobx-react'; -import Colors from 'app/constants/Colors'; -import * as stores from 'app/skyhitz-common'; -import cursorPointer from 'app/constants/CursorPointer'; -type Stores = typeof stores; - -@inject((stores: Stores) => ({ - uploadProfilePhoto: stores.editProfileStore.uploadProfilePhoto.bind( - stores.editProfileStore - ), -})) -export default class EditProfilePhotoBtn extends React.Component { - async launchImageLibrary() { - let image = await ImagePicker.launchImageLibraryAsync({ - mediaTypes: ImagePicker.MediaTypeOptions.Images, - allowsEditing: true, - aspect: [1, 1], - quality: 0.7, - base64: true, - exif: true, - }); - if (image && !image.cancelled) { - this.props.uploadProfilePhoto(image); - } - } - async changeProfilePhoto() { - if (Platform.OS === 'ios' || Platform.OS === 'android') { - const { status } = await Permissions.askAsync(Permissions.CAMERA_ROLL); - if (status === 'granted') { - return this.launchImageLibrary(); - } - } - this.launchImageLibrary(); - } - render() { - return ( - - Change Profile Photo - - ); - } -} - -const styles = StyleSheet.create({ - btn: { - marginBottom: 20, - }, -}); diff --git a/modules/profile/EditProfileScreen.tsx b/modules/profile/EditProfileScreen.tsx deleted file mode 100644 index 6985886..0000000 --- a/modules/profile/EditProfileScreen.tsx +++ /dev/null @@ -1,348 +0,0 @@ -import React from 'react'; -import { - View, - StyleSheet, - Text, - TextInput, - Pressable, - Platform, - KeyboardAvoidingView, -} from 'react-native'; -import { inject } from 'mobx-react'; -import Colors from 'app/constants/Colors'; -import { - UserAvatarMedium, - UserAvatarMediumWithUrlOnly, - LoadingUserAvatar, -} from 'app/modules/ui/UserAvatar'; -import EditProfilePhotoBtn from 'app/modules/profile/EditProfilePhotoBtn'; -import * as stores from 'app/skyhitz-common'; -import LargeBtn from '../ui/LargeBtn'; -import cursorPointer from 'app/constants/CursorPointer'; -import AccountBoxIcon from 'app/modules/ui/icons/account-box'; -import PersonOutlineIcon from 'app/modules/ui/icons/person-outline'; -import MailOutlineIcon from 'app/modules/ui/icons/mail-outline'; -import LogoutIcon from 'app/modules/ui/icons/logout'; -import InfoCirlceIcon from 'app/modules/ui/icons/info-circle'; -import AsyncStorage from '@react-native-async-storage/async-storage'; - -type Stores = typeof stores; - -@inject((stores: Stores) => ({ - profile: stores.editProfileStore.profile, - avatarUrl: stores.editProfileStore.avatarUrl, - loadingAvatar: stores.editProfileStore.loadingAvatar, - displayName: stores.editProfileStore.displayName, - description: stores.editProfileStore.description, - username: stores.editProfileStore.username, - email: stores.editProfileStore.email, - updateDisplayName: stores.editProfileStore.updateDisplayName.bind( - stores.editProfileStore - ), - updateDescription: stores.editProfileStore.updateDescription.bind( - stores.editProfileStore - ), - updateUsername: stores.editProfileStore.updateUsername.bind( - stores.editProfileStore - ), - updateEmail: stores.editProfileStore.updateEmail.bind( - stores.editProfileStore - ), - logOut: stores.sessionStore.signOut.bind(stores.sessionStore), - credits: stores.paymentsStore.credits, - validationError: stores.editProfileStore.validationError, -})) -export default class EditProfileScreen extends React.Component { - async handleLogOut() { - await this.props.logOut(); - await AsyncStorage.multiRemove(await AsyncStorage.getAllKeys()); - this.props.navigation.navigate( - Platform.OS === 'web' ? 'WebApp' : 'AuthScreen' - ); - } - async handleWithdrawal() { - this.props.navigation.navigate('WithdrawalModal'); - } - renderAvatar() { - if (this.props.loadingAvatar) { - return ; - } - if (this.props.avatarUrl) { - return UserAvatarMediumWithUrlOnly(this.props.avatarUrl); - } - return UserAvatarMedium(this.props.profile); - } - renderWithdrawalXLM() { - if (this.props.credits && this.props.credits > 0) { - return ( - - Credits - - - ); - } - return null; - } - render() { - return ( - - - - {this.props.validationError} - - - {this.renderAvatar()} - - - - - - - - - this.props.updateDisplayName(t)} - maxLength={30} - /> - - - - - - this.props.updateDescription(t)} - maxLength={150} - /> - - - - - - this.props.updateUsername(t)} - maxLength={30} - /> - - - Private Information - - - - - - - this.props.updateEmail(t)} - maxLength={34} - /> - - - {this.renderWithdrawalXLM()} - More - - - - - - - Log Out - - - - - - ); - } -} - -const formPadding = 20; -const maxHeight = 50; - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: Colors.listItemBackground, - }, - headerWrap: { - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - }, - inputContainerTop: { - paddingLeft: formPadding, - paddingRight: formPadding, - marginTop: 0, - flex: 1, - maxHeight: maxHeight * 3, - borderBottomColor: Colors.transparent, - borderBottomWidth: 1, - borderTopColor: Colors.transparent, - borderTopWidth: 1, - }, - inputContainerMiddle: { - paddingLeft: formPadding, - paddingRight: formPadding, - marginTop: 0, - flex: 1, - maxHeight: maxHeight, - borderBottomColor: Colors.transparent, - borderBottomWidth: 1, - borderTopColor: Colors.transparent, - borderTopWidth: 1, - }, - inputContainerBottom: { - paddingLeft: formPadding, - paddingRight: formPadding, - marginTop: 0, - width: '100%', - height: maxHeight, - borderBottomColor: Colors.transparent, - borderBottomWidth: 1, - borderTopColor: Colors.transparent, - borderTopWidth: 1, - flexDirection: 'row', - alignItems: 'center', - }, - withdrawalContainer: { - paddingLeft: formPadding, - paddingRight: formPadding, - marginTop: 0, - flex: 1, - height: 50, - minHeight: 50, - borderBottomColor: Colors.transparent, - borderBottomWidth: 1, - borderTopColor: Colors.transparent, - borderTopWidth: 1, - }, - input: { - backgroundColor: Colors.transparent, - color: Colors.defaultTextLight, - fontSize: 14, - paddingLeft: 36, - }, - withdrawInput: { - backgroundColor: Colors.transparent, - color: Colors.defaultTextLight, - fontSize: 14, - paddingLeft: 36, - bottom: 0, - }, - field: { - height: maxHeight, - borderBottomColor: Colors.dividerBackground, - borderBottomWidth: 0.5, - justifyContent: 'flex-start', - width: '100%', - flexDirection: 'row', - alignItems: 'center', - }, - withdrawalXLMField: { - paddingLeft: 18, - height: 80, - width: '100%', - justifyContent: 'center', - paddingBottom: 10, - marginTop: 20, - }, - fieldWithoutBorder: { - height: maxHeight, - justifyContent: 'flex-start', - width: '100%', - flexDirection: 'row', - alignItems: 'center', - }, - placeholderIcon: { - position: 'absolute', - left: 0, - backgroundColor: Colors.transparent, - }, - coinIcon: { - position: 'absolute', - left: 20, - backgroundColor: Colors.transparent, - }, - privateInfo: { - paddingLeft: 18, - paddingTop: 30, - paddingBottom: 5, - fontSize: 14, - fontWeight: 'bold', - color: Colors.defaultTextLight, - }, - creditsInfo: { - paddingTop: 20, - paddingBottom: 20, - fontSize: 14, - fontWeight: 'bold', - color: Colors.defaultTextLight, - }, - errorContainer: { - maxHeight: 40, - backgroundColor: Colors.errorBackground, - paddingLeft: formPadding, - paddingRight: formPadding, - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, - error: { - color: Colors.white, - }, -}); diff --git a/modules/profile/ProfileEntryListView.tsx b/modules/profile/ProfileEntryListView.tsx deleted file mode 100644 index 1c9b663..0000000 --- a/modules/profile/ProfileEntryListView.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { ScrollView } from 'react-native'; -import { inject } from 'mobx-react'; -import EntryRow from 'app/modules/ui/EntryRow'; -import SearchingLoader from 'app/modules/ui/SearchingLoader'; -import Colors from 'app/constants/Colors'; -import BottomPlaceholder from 'app/modules/ui/BottomPlaceholder'; -import * as L from 'list'; -import * as stores from 'app/skyhitz-common'; -type Stores = typeof stores; - -const ProfileEntryListView = inject((stores: Stores) => ({ - loadAndPlay: stores.playerStore.loadAndPlay.bind(stores.playerStore), - entries: stores.profileStore.entries, - loading: stores.profileStore.loadingEntries, -}))(({ loadAndPlay, entries, loading }: any) => ( - - {SearchingLoader(loading)} - {L.map( - (entry: any) => ( - - ), - entries - )} - - -)); - -export default ProfileEntryListView; diff --git a/modules/providers/Providers.tsx b/modules/providers/Providers.tsx deleted file mode 100644 index 664a401..0000000 --- a/modules/providers/Providers.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Provider } from 'mobx-react'; -import React from 'react'; -import { - sessionStore, - signUpValidationStore, - signInValidationStore, - usernameAndEmailValidationStore, - playerStore, - usersSearchStore, - entriesSearchStore, - inputSearchStore, - profileStore, - editProfileStore, - likesStore, - entryStore, - userEntriesStore, - paymentsStore, - walletConnectStore, -} from 'app/skyhitz-common'; - -export default function Providers(props) { - return ( - - {props.children} - - ); -} diff --git a/modules/search/SearchEntryList.tsx b/modules/search/SearchEntryList.tsx deleted file mode 100644 index 3f23568..0000000 --- a/modules/search/SearchEntryList.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import { ScrollView } from 'react-native'; -import { inject } from 'mobx-react'; -import EntryRow from 'app/modules/ui/EntryRow'; -import SearchingLoader from 'app/modules/ui/SearchingLoader'; -import Colors from 'app/constants/Colors'; -import BottomPlaceholder from 'app/modules/ui/BottomPlaceholder'; -import * as stores from 'app/skyhitz-common'; -import * as L from 'list'; -import ResponsiveLayout from '../ui/ResponsiveLayout'; -type Stores = typeof stores; - -const SearchEntryList = inject((stores: Stores) => ({ - loadPlayAndPushToCueList: stores.playerStore.loadPlayAndPushToCueList.bind( - stores.playerStore - ), - entries: stores.entriesSearchStore.entries, - searching: stores.entriesSearchStore.searching, - query: stores.entriesSearchStore.query, - disablePlaylistMode: stores.playerStore.disablePlaylistMode.bind( - stores.playerStore - ), -}))( - ({ - loadPlayAndPushToCueList, - entries, - searching, - query, - disablePlaylistMode, - }: any) => ( - - - {SearchingLoader(searching, query)} - - {L.map( - (entry: any) => ( - - ), - entries - )} - - - - ) -); - -export default SearchEntryList; diff --git a/modules/search/SearchEntryView.tsx b/modules/search/SearchEntryView.tsx deleted file mode 100644 index b692577..0000000 --- a/modules/search/SearchEntryView.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { useCallback, useEffect } from 'react'; -import { observer } from 'mobx-react'; -import SearchEntryList from 'app/modules/search/SearchEntryList'; -import RecentlyAdded from 'app/modules/search/RecentlyAdded'; -import { useFocusEffect } from '@react-navigation/native'; -import { Stores } from 'app/functions/Stores'; - -export default observer(() => { - let { entriesSearchStore, inputSearchStore } = Stores(); - - useFocusEffect( - useCallback(() => { - inputSearchStore.updateSearchType('entries'); - }, []) - ); - - const handleRecentlyAdded = async () => { - await entriesSearchStore.getRecentlyAdded(); - }; - - useEffect(() => { - handleRecentlyAdded(); - }, []); - - if (entriesSearchStore.active) { - return ; - } - return ; -}); diff --git a/modules/search/SearchHeader.tsx b/modules/search/SearchHeader.tsx deleted file mode 100644 index 35e1125..0000000 --- a/modules/search/SearchHeader.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { Platform } from 'react-native'; -import { inject } from 'mobx-react'; -import Colors from 'app/constants/Colors'; -import SearchBar from 'app/modules/ui/searchbar/SearchBar'; -import * as stores from 'app/skyhitz-common'; -type Stores = typeof stores; - -let platform = Platform.OS === 'ios' ? 'ios' : 'android'; - -@inject((stores: Stores) => ({ - inputSearchStore: stores.inputSearchStore, -})) -class SearchHeader extends React.Component { - state = { - value: '', - }; - - changeText = (value: any) => { - this.setState({ value }); - }; - - render() { - return ( - { - this.props.inputSearchStore.search(q); - this.changeText(q); - }} - placeholder="Search" - icon={{ - style: { top: 15 }, - color: Colors.searchTextColor, - name: 'search', - }} - clearIcon={true} - value={this.state.value} - autoCorrect={false} - placeholderTextColor={Colors.searchTextColor} - inputStyle={{ - height: 30, - margin: 7.5, - borderRadius: 5, - color: Colors.searchTextColor, - }} - /> - ); - } -} - -export default SearchHeader; diff --git a/modules/search/SearchUserList.tsx b/modules/search/SearchUserList.tsx deleted file mode 100644 index 8b8a5da..0000000 --- a/modules/search/SearchUserList.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import { ScrollView } from 'react-native'; -import { inject } from 'mobx-react'; -import UserRow from 'app/modules/ui/UserRow'; -import SearchingLoader from 'app/modules/ui/SearchingLoader'; -import Colors from 'app/constants/Colors'; -import BottomPlaceholder from 'app/modules/ui/BottomPlaceholder'; -import * as stores from 'app/skyhitz-common'; -import * as L from 'list'; -import ResponsiveLayout from '../ui/ResponsiveLayout'; -type Stores = typeof stores; - -const UserSearchList = inject((stores: Stores) => ({ - users: stores.usersSearchStore.users, - searching: stores.usersSearchStore.searching, - query: stores.usersSearchStore.query, -}))(({ users, searching, query }: any) => ( - - - {SearchingLoader(searching, query)} - {L.map( - (user: { id: string | number | undefined }) => ( - - ), - users - )} - - - -)); - -export default UserSearchList; diff --git a/modules/search/SearchUserView.tsx b/modules/search/SearchUserView.tsx deleted file mode 100644 index e0af231..0000000 --- a/modules/search/SearchUserView.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, { useCallback } from 'react'; -import { observer } from 'mobx-react'; -import SearchUserList from 'app/modules/search/SearchUserList'; -import TopRecentUserView from 'app/modules/search/TopRecentUserView'; -import { useFocusEffect } from '@react-navigation/native'; -import { Stores } from 'app/functions/Stores'; - -export default observer(() => { - let { usersSearchStore, inputSearchStore } = Stores(); - - useFocusEffect( - useCallback(() => { - inputSearchStore.updateSearchType('users'); - }, []) - ); - - if (usersSearchStore.active) { - return ; - } - return ; -}); diff --git a/modules/search/TopRecentUserView.tsx b/modules/search/TopRecentUserView.tsx deleted file mode 100644 index e485593..0000000 --- a/modules/search/TopRecentUserView.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { ScrollView, StyleSheet } from 'react-native'; -import TopUserSearchView from 'app/modules/search/TopUserSearchView'; -import BottomPlaceholder from 'app/modules/ui/BottomPlaceholder'; -import Colors from 'app/constants/Colors'; -import ResponsiveLayout from '../ui/ResponsiveLayout'; - -const TopRecentUserView = () => ( - - - - - - -); - -const styles = StyleSheet.create({ - scrollView: { - backgroundColor: Colors.listItemBackground, - flex: 1, - }, -}); - -export default TopRecentUserView; diff --git a/modules/search/TopUserSearchView.tsx b/modules/search/TopUserSearchView.tsx deleted file mode 100644 index 1cd1cd1..0000000 --- a/modules/search/TopUserSearchView.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import { View, Text, StyleSheet } from 'react-native'; -import { inject } from 'mobx-react'; -import Colors from 'app/constants/Colors'; -import UserRow from 'app/modules/ui/UserRow'; -import SearchingLoader from 'app/modules/ui/SearchingLoader'; -import * as L from 'list'; -import * as stores from 'app/skyhitz-common'; -type Stores = typeof stores; - -@inject((stores: Stores) => ({ - getTopSearches: stores.usersSearchStore.getTopSearches.bind( - stores.usersSearchStore - ), - topSearches: stores.usersSearchStore.topSearches, - loadingTopSearches: stores.usersSearchStore.loadingTopSearches, -})) -export default class TopUserSearchView extends React.Component { - componentDidMount() { - this.props.getTopSearches(); - } - render() { - if (!this.props.loadingTopSearches && !this.props.topSearches.length) { - return null; - } - return ( - - TOP - {SearchingLoader(this.props.loadingTopSearches)} - {L.map( - (user: { id: string | number | undefined }) => ( - - ), - this.props.topSearches - )} - - ); - } -} - -const styles = StyleSheet.create({ - recentText: { - color: Colors.defaultTextLight, - fontSize: 14, - paddingTop: 10, - paddingLeft: 10, - }, -}); diff --git a/modules/ui/DoneEditBtn.tsx b/modules/ui/DoneEditBtn.tsx deleted file mode 100644 index 3dbb42f..0000000 --- a/modules/ui/DoneEditBtn.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { Pressable, StyleSheet, Text } from 'react-native'; -import Colors from 'app/constants/Colors'; -import { useLinkTo } from '@react-navigation/native'; -import { Stores } from 'app/functions/Stores'; -import { observer } from 'mobx-react'; -import cursorPointer from 'app/constants/CursorPointer'; - -const DoneEditBtn = observer(() => { - const linkTo = useLinkTo(); - const { editProfileStore } = Stores(); - - const closeProfileModal = () => { - editProfileStore.updateProfile(); - linkTo('/dashboard/profile'); - }; - return ( - - - Done - - - ); -}); - -export default DoneEditBtn; - -const styles = StyleSheet.create({ - btn: { - paddingRight: 10, - }, - white: { - color: Colors.white, - }, -}); diff --git a/modules/ui/LogOutBtn.tsx b/modules/ui/LogOutBtn.tsx deleted file mode 100644 index 48bdd96..0000000 --- a/modules/ui/LogOutBtn.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { Pressable, StyleSheet } from 'react-native'; -import Colors from 'app/constants/Colors'; -import { Stores } from 'app/functions/Stores'; -import { observer } from 'mobx-react'; -import LogOutIcon from 'app/modules/ui/icons/logout'; -import { useLinkTo } from '@react-navigation/native'; - -export default observer(() => { - const linkTo = useLinkTo(); - const { sessionStore, likesStore, walletConnectStore } = Stores(); - const handleLogOut = async () => { - await walletConnectStore.disconnect(); - await sessionStore.signOut(); - linkTo('/'); - likesStore.clearLikes(); - }; - return ( - - - - ); -}); - -const styles = StyleSheet.create({ - btn: { - paddingRight: 10, - }, -}); diff --git a/package.json b/package.json index f8db423..c6863d0 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "license": "MIT", "main": "node_modules/expo/AppEntry.js", "dependencies": { - "@apollo/client": "^3.5.6", "@expo/html-elements": "^0.1.0", "@expo/match-media": "^0.1.0", "@react-native-async-storage/async-storage": "~1.15.0", @@ -56,11 +55,9 @@ "expo-linking": "~2.4.2", "expo-next-react-navigation": "0.0.25", "expo-permissions": "~13.0.3", - "graphql": "^16.2.0", - "list": "^2.0.19", + "graphql": "^16.5.0", + "graphql-request": "^4.3.0", "lodash.debounce": "4.0.8", - "mobx": "5.15.4", - "mobx-react": "6.2.2", "next": "^12.0.1", "next-fonts": "^1.5.1", "next-images": "^1.8.1", @@ -79,6 +76,7 @@ "react-native-tab-view": "^3.0.1", "react-native-web": "0.17.1", "react-responsive": "8.0.3", + "recoil": "^0.7.4", "setimmediate": "^1.0.5", "stellar-base": "^8.0.1", "twrnc": "^3.3.2" diff --git a/pages/_document.tsx b/pages/_document.tsx index 886b530..ccf21fb 100644 --- a/pages/_document.tsx +++ b/pages/_document.tsx @@ -62,7 +62,7 @@ class CustomDocument extends Document { /> - Skyhitz - Beats market for music creators + Skyhitz - Music NFTs on Stellar { console.log('error', error); /* Log the error to an error reporting service */ }; -const Providers = lazy(() => import('app/modules/providers/Providers')); - -import LoadingScreen from 'app/modules/accounts/LoadingScreen'; -const SuspenseLoading = (props) => ( - }>{props.children} -); -const ProvidersSuspense = (props) => ( - - - -); +import LoadingScreen from 'app/src/accounts/LoadingScreen'; export default () => { const isLoadingComplete = useCachedResources(); @@ -29,10 +21,10 @@ export default () => { return ; } return ( - - + + - + ); }; diff --git a/skyhitz-common/index.ts b/skyhitz-common/index.ts deleted file mode 100644 index 8420b10..0000000 --- a/skyhitz-common/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './src'; diff --git a/skyhitz-common/src/backends/apollo-client.backend.ts b/skyhitz-common/src/backends/apollo-client.backend.ts deleted file mode 100644 index 0f0b589..0000000 --- a/skyhitz-common/src/backends/apollo-client.backend.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - ApolloClient, - InMemoryCache, - from, - createHttpLink, -} from '@apollo/client'; -import { onError } from '@apollo/client/link/error'; -import { setContext } from '@apollo/client/link/context'; - -import { observable } from 'mobx'; -import { Config } from '../config'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { userDataKey } from '../constants/constants'; - -export let forceSignOut = observable.box(false); - -const httpLink = createHttpLink({ - uri: Config.GRAPHQL_URL, -}); - -const authLink = setContext(async (_, { headers }) => { - // get the authentication token from local storage if it exists - const userData = await AsyncStorage.getItem(userDataKey); - if (userData) { - const { jwt } = JSON.parse(userData); - if (jwt) { - return { - headers: { - ...headers, - authorization: jwt ? `Bearer ${jwt}` : '', - }, - }; - } - } - return { - headers: { - ...headers, - }, - }; -}); - -const logoutLink = onError(({ networkError }: any) => { - if ( - (networkError && networkError.statusCode === 401) || - networkError?.statusText === 'Unauthorized' - ) - forceSignOut.set(true); -}); - -export const client = new ApolloClient({ - link: from([authLink, logoutLink, httpLink]), - cache: new InMemoryCache(), -}); diff --git a/skyhitz-common/src/backends/payments.backend.ts b/skyhitz-common/src/backends/payments.backend.ts deleted file mode 100644 index 8bfb16c..0000000 --- a/skyhitz-common/src/backends/payments.backend.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { client } from './apollo-client.backend'; -import { gql } from '@apollo/client'; - -export class PaymentsBackend { - async subscribe(cardToken: string) { - return client - .mutate({ - mutation: gql` - mutation { - subscribeUser(cardToken: "${cardToken}") - } - `, - }) - .then((data: any) => data.data) - .then(({ subscribeUser }) => subscribeUser); - } - async buyCredits(cardToken: string, amount: number) { - return client - .mutate({ - mutation: gql` - mutation { - buyCredits(cardToken: "${cardToken}", amount: ${amount}) - } - `, - }) - .then((data: any) => data.data) - .then(({ buyCredits }) => buyCredits); - } - async getXLMPrice() { - return client - .query({ - query: gql` - { - xlmPrice - } - `, - fetchPolicy: 'network-only', - }) - .then((data: any) => data.data) - .then(({ xlmPrice }) => xlmPrice); - } - async withdrawToExternalWallet(address: string, amount: number) { - return client - .mutate({ - mutation: gql` - mutation { - withdrawToExternalWallet(address: "${address}", amount: ${amount}) - } - `, - }) - .then((data: any) => data.data) - .then(({ withdrawToExternalWallet }) => withdrawToExternalWallet); - } - async refreshSubscription() { - return client - .query({ - query: gql` - { - paymentsInfo { - subscribed - credits - } - } - `, - fetchPolicy: 'network-only', - }) - .then((data: any) => data.data) - .then(({ paymentsInfo }) => paymentsInfo); - } -} - -export const paymentsBackend = new PaymentsBackend(); diff --git a/skyhitz-common/src/backends/user.backend.ts b/skyhitz-common/src/backends/user.backend.ts deleted file mode 100644 index de40d8d..0000000 --- a/skyhitz-common/src/backends/user.backend.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { client } from './apollo-client.backend'; -import { gql } from '@apollo/client'; -import { SignUpForm } from '../types'; - -export class UserBackend { - async getAuthenticatedUser() { - return client - .query({ - query: gql` - { - authenticatedUser { - avatarUrl - displayName - username - id - jwt - publishedAt - email - description - } - } - `, - }) - .then((data: any) => data.data) - .then(({ authenticatedUser }) => authenticatedUser); - } - - async signUp({ displayName, email, username, publicKey }: SignUpForm) { - return client - .mutate({ - mutation: gql` - mutation { - createUserWithEmail(displayName: "${displayName}", email: "${email}", username: "${username}",publicKey: "${publicKey}"){ - avatarUrl - displayName - username - id - jwt - publishedAt - email - description - publicKey - } - } - `, - }) - .then((data: any) => data.data) - .then(({ createUserWithEmail }) => createUserWithEmail) - .catch(({ graphQLErrors }) => { - let [{ message }] = graphQLErrors; - throw message; - }); - } - - async requestToken(usernameOrEmail, publicKey) { - return client - .mutate({ - mutation: gql` - mutation { - requestToken(usernameOrEmail: "${usernameOrEmail}", publicKey: "${publicKey}") - } - `, - }) - .then(({ data }: any) => { - return data; - }) - .catch(({ graphQLErrors }) => { - let [{ message }] = graphQLErrors; - throw message; - }); - } - - async signIn(token?: string, uid?: string, xdr?: string) { - return client - .mutate({ - mutation: gql` - mutation { - signIn(token: "${token}", uid: "${uid}", signedXDR: "${xdr}"){ - avatarUrl - displayName - username - id - jwt - publishedAt - email - description - publicKey - } - } - `, - }) - .then((data: any) => data.data) - .then(({ signIn }) => signIn) - .catch(({ graphQLErrors }) => { - let [{ message }] = graphQLErrors; - throw message; - }); - } - - async updateUser( - avatarUrl: string, - displayName: string, - description: string, - username: string, - email: string - ) { - return client - .mutate({ - mutation: gql` - mutation { - updateUser(avatarUrl: "${avatarUrl}", displayName: "${displayName}", description: "${description}", username: "${username}", email: "${email}"){ - avatarUrl - displayName - username - id - publishedAt - email - description - } - } - `, - }) - .then((data: any) => data.data) - .then(({ updateUser }) => updateUser) - .catch(({ graphQLErrors }) => { - let [{ message }] = graphQLErrors; - throw message; - }); - } -} - -export const userBackend = new UserBackend(); diff --git a/skyhitz-common/src/index.ts b/skyhitz-common/src/index.ts deleted file mode 100644 index 36d472a..0000000 --- a/skyhitz-common/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './stores'; -export * from './models'; \ No newline at end of file diff --git a/skyhitz-common/src/stores/edit-profile.store.ts b/skyhitz-common/src/stores/edit-profile.store.ts deleted file mode 100644 index 3db6cbc..0000000 --- a/skyhitz-common/src/stores/edit-profile.store.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { User } from '../models/user.model'; -import { observable, observe, action } from 'mobx'; -import { userBackend } from '../backends/user.backend'; -import { SessionStore } from './session.store'; -import { nftStorageApi, imagesGateway } from '../constants/constants'; - -export class EditProfileStore { - @observable error: string | undefined | unknown; - @observable avatarUrl: string | undefined; - @observable displayName: string | undefined; - @observable description: string | undefined; - @observable username: string | undefined; - @observable email: string | undefined; - @observable profile: User | undefined; - @observable loadingAvatar: boolean | undefined; - disposer; - - constructor(public sessionStore: SessionStore) { - this.disposer = observe(sessionStore.session, ({ object }) => { - this.profile = object.user; - if (!this.profile) { - return; - } - let { - avatarUrl, - displayName, - description, - username, - email, - } = this.profile; - this.avatarUrl = avatarUrl; - this.displayName = displayName; - this.description = description; - this.username = username; - this.email = email; - }); - } - - async uploadProfilePhoto(image: any) { - const isPng = image.uri.startsWith('data:image/png'); - if (!isPng) { - this.error = 'Only png files supported!'; - return; - } - if (image.height !== image.width) { - return (this.error = 'Only square images supported!'); - } - const blobRes = await fetch(image.uri); - const file = await blobRes.blob(); - if (!this.sessionStore.user) return; - this.loadingAvatar = true; - let res = await fetch(`${nftStorageApi}/upload`, { - method: 'POST', - body: file, - headers: new Headers({ - Authorization: `Bearer ${process.env.NEXT_PUBLIC_NFT_STORAGE_API_KEY}`, - }), - }); - let { value, ok } = await res.json(); - - if (ok) { - this.updateAvatarUrl(`${imagesGateway}/${value.cid}`); - } - this.loadingAvatar = false; - } - - @action - updateAvatarUrl = (text: string) => { - this.avatarUrl = text; - }; - - @action - updateDisplayName = (text: string) => { - this.displayName = text; - }; - - @action - updateDescription = (text: string) => { - this.description = text; - }; - - @action - updateUsername = (text: string) => { - this.username = text; - }; - - @action - updateEmail = (text: string) => { - this.email = text; - }; - - get validationError() { - if (!this.avatarUrl) { - return 'Upload a profile picture.'; - } - - if (!this.displayName) { - return 'Add a display name.'; - } - - if (!this.description) { - return 'Add a description.'; - } - - if (!this.username) { - return 'Add a username.'; - } - - if (!this.email) { - return 'Add an email.'; - } - - return null; - } - - get canUpdate() { - return ( - this.avatarUrl && - this.displayName && - this.description && - this.username && - this.email - ); - } - - async updateProfile() { - let user; - try { - user = await userBackend.updateUser( - this.avatarUrl as string, - this.displayName as string, - this.description as string, - this.username as string, - this.email as string - ); - } catch (e) { - this.error = e; - return; - } - if (user) { - return await this.sessionStore.refreshUser(); - } - } -} diff --git a/skyhitz-common/src/stores/entries-search.store.ts b/skyhitz-common/src/stores/entries-search.store.ts deleted file mode 100644 index fad8b83..0000000 --- a/skyhitz-common/src/stores/entries-search.store.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { Entry } from '../models/entry.model'; -import { entriesBackend } from '../backends/entries.backend'; -import { observable, observe, computed } from 'mobx'; -import * as L from 'list'; - -const debounce = require('lodash.debounce'); - -export class EntriesSearchStore { - @observable searching: boolean = false; - @observable loadingRecentSearches: boolean = false; - @observable loadingTopSearches: boolean = false; - @observable loadingRecentlyAdded: boolean = false; - @observable load: boolean = false; - @observable loadingTopChart: boolean = false; - @observable query: string = ''; - @observable public entries: L.List = L.from([]); - @observable public recentSearches: L.List = L.from([]); - @observable public recentlyAdded: Entry[] = []; - @observable public hasMoreRecentlyAdded = true; - @observable topChart: Entry[] = []; - @observable public hasMoreTopChart = true; - disposer: any; - - @computed get active() { - return !!this.query; - } - - constructor(public queryObservable: any) { - this.disposer = observe(queryObservable, ({ object }) => { - if (object.type === 'entries' && object.q !== this.query) { - this.query = object.q; - this.searching = true; - this.debouncedSearch(object.q); - } - }); - } - - public searchEntries(q: string) { - return entriesBackend.search(q).then((results) => { - let entries: Entry[] = results.map((result: any) => new Entry(result)); - this.setEntries(L.from(entries)); - this.searching = false; - }); - } - - public debouncedSearch = debounce(this.searchEntries, 400); - - public setEntries(entries: L.List) { - this.entries = entries; - } - - public setRecentSearches(entries: L.List) { - this.recentSearches = entries; - } - - public setTopChart(entries: Entry[]) { - this.topChart = entries; - } - - public setRecentlyAdded(entries: Entry[]) { - this.recentlyAdded = entries; - } - - public getTopChart() { - this.loadingTopChart = true; - return entriesBackend.getTopChart().then((entries) => { - this.setTopChart(entries); - this.loadingTopChart = false; - }); - } - - public loadMoreTopChart(page: number) { - this.loadingTopChart = true; - return entriesBackend.getTopChart(page).then((entries) => { - if (entries.length == 0) { - this.hasMoreTopChart = false; - this.loadingTopChart = false; - return; - } - this.setTopChart([...this.topChart, ...entries]); - this.loadingTopChart = false; - return this.topChart; - }); - } - - public getRecentSearches() { - this.loadingRecentSearches = true; - return entriesBackend.getRecentSearches().then((entries) => { - this.setRecentSearches(L.from(entries)); - this.loadingRecentSearches = false; - }); - } - - public getRecentlyAdded() { - this.loadingRecentlyAdded = true; - return entriesBackend.getRecentlyAdded().then((entries) => { - this.setRecentlyAdded(entries); - this.loadingRecentlyAdded = false; - return this.recentlyAdded; - }); - } - - public loadMoreRecentlyAdded(page: number) { - this.loadingRecentlyAdded = true; - return entriesBackend.getRecentlyAdded(page).then((entries) => { - if (entries.length == 0) { - this.hasMoreRecentlyAdded = false; - this.loadingRecentlyAdded = false; - return; - } - this.setRecentlyAdded([...this.recentlyAdded, ...entries]); - this.loadingRecentlyAdded = false; - return this.recentlyAdded; - }); - } -} diff --git a/skyhitz-common/src/stores/entry.store.ts b/skyhitz-common/src/stores/entry.store.ts deleted file mode 100644 index 3574e82..0000000 --- a/skyhitz-common/src/stores/entry.store.ts +++ /dev/null @@ -1,276 +0,0 @@ -import { observable, action, computed } from 'mobx'; -import { nftStorageApi } from '../constants/constants'; -import { entriesBackend } from '../backends/entries.backend'; - -export class EntryStore { - @observable uploadingVideo: boolean = false; - @observable - uploadingError!: string; - @observable loadingVideo: boolean = false; - @observable - loadingImage!: boolean; - @observable - description!: string; - @observable - issuer = ''; - @observable - title!: string; - @observable - artist!: string; - @observable - availableForSale!: boolean; - @observable - price: number | undefined; - @observable - equityForSale: number = 1; - @observable - equityForSalePercentage: string = '1 %'; - @observable - filesProgress: { [key: string]: number } = {}; - - @observable - creating: boolean = false; - - @observable - imageBlob: Blob | undefined; - - @observable - videoBlob: Blob | undefined; - - @action - clearUploadingError() { - this.uploadingError = ''; - } - - @action - setIssuer(issuer) { - this.issuer = issuer; - } - - @action - setUploadingError(error) { - this.uploadingError = error; - } - - @action - setLoadingVideo(loading: boolean) { - this.loadingVideo = loading; - } - - @action - updateLoadingImage = (state: boolean) => { - this.loadingImage = state; - }; - - @action - updateUploadingVideo = (state: boolean) => { - this.uploadingVideo = state; - }; - - @action - updateDescription = (text: string) => { - this.description = text; - }; - - @action - updateTitle = (text: string) => { - this.title = text; - }; - - @action - updateArtist = (text: string) => { - this.artist = text; - }; - - @action - updateAvailableForSale = (state: boolean) => { - this.availableForSale = state; - }; - - @action - updatePrice = (price: number) => { - this.price = price; - }; - - @action - updateEquityForSalePercentage = (value: number) => { - this.equityForSale = value; - this.equityForSalePercentage = `${value ? value : 0} %`; - }; - - constructor() {} - - setImageBlob(imageBlob) { - this.imageBlob = imageBlob; - } - - setVideoBlob(videoBlob) { - this.videoBlob = videoBlob; - } - - async uploadFile(file: any, id: string) { - return new Promise((resolve: (value: string) => void, reject) => { - this.filesProgress[id] = 0; - let xhr = new XMLHttpRequest(); - xhr.open('POST', `${nftStorageApi}/upload`, true); - xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - xhr.setRequestHeader( - 'Authorization', - `Bearer ${process.env.NEXT_PUBLIC_NFT_STORAGE_API_KEY}` - ); - - xhr.upload.addEventListener('progress', (e) => { - const progress = Math.round((e.loaded * 100.0) / e.total); - - this.filesProgress[id] = progress; - if (progress === 100) { - delete this.filesProgress[id]; - } - }); - - xhr.onreadystatechange = () => { - if (xhr.readyState == 4 && xhr.status == 200) { - let { value, ok } = JSON.parse(xhr.responseText); - if (!ok) { - this.uploadingError = 'Something went wrong!'; - reject(); - return; - } - resolve(value.cid); - } - }; - - xhr.send(file); - }); - } - - async storeNFT() { - const name = `${this.artist} - ${this.title}`; - const ipfsProtocol = 'ipfs://'; - - const code = `${this.title}${this.artist}` - .normalize('NFD') - .replace(/\p{Diacritic}/gu, '') - .replace(/ /g, '') - .replace(/-/g, '') - .replace(/[^0-9a-z]/gi, '') - .substr(0, 12) - .toUpperCase(); - - const [imageCid, videoCid] = [ - await this.uploadFile(this.imageBlob, 'image'), - await this.uploadFile(this.videoBlob, 'video'), - ]; - - const imageUrl = `${ipfsProtocol}${imageCid}`; - const videoUrl = `${ipfsProtocol}${videoCid}`; - - const issuer = await entriesBackend.getIssuer(videoCid); - this.setIssuer(issuer); - if (!issuer) throw 'could not generate issuer'; - - const json = { - name: name, - description: this.description, - code: code, - issuer: issuer, - domain: 'skyhitz.io', - supply: 1, - image: imageUrl, - animation_url: videoUrl, - video: videoUrl, - url: videoUrl, - }; - - const blob = new Blob([JSON.stringify(json)], { type: 'application/json' }); - - const nftCid = await this.uploadFile(blob, 'meta'); - return { videoCid, nftCid, imageUrl, videoUrl, code }; - } - - clearStore() { - this.updateUploadingVideo(false); - this.updateLoadingImage(false); - this.updateDescription(''); - this.updateTitle(''); - this.updateArtist(''); - this.updateAvailableForSale(false); - this.updatePrice(0); - this.updateEquityForSalePercentage(1); - this.creating = false; - } - - get currentUpload() { - return Object.keys(this.filesProgress).includes('video') - ? 'video' - : Object.keys(this.filesProgress).includes('meta') - ? 'meta' - : 'none'; - } - - get progress() { - return Math.min(...Object.values(this.filesProgress)); - } - - @computed - get canCreate() { - return ( - this.imageBlob && - this.videoBlob && - this.description && - this.title && - this.artist - ); - } - - async indexEntry(issuer = this.issuer) { - if (!issuer) return false; - return await entriesBackend.indexEntry(issuer); - } - - async create() { - this.creating = true; - const { - videoCid, - nftCid, - imageUrl, - videoUrl, - code, - } = await this.storeNFT(); - if (!nftCid || !imageUrl || !videoUrl) { - this.setUploadingError('Could not store NFT'); - return; - } - return await entriesBackend.createFromUpload( - videoCid, - nftCid, - code, - this.availableForSale, - this.price, - this.equityForSale - ); - } - - async updatePricing(entry: any) { - if (!this.availableForSale) { - return; - } - if (!this.price) { - return; - } - if (!this.equityForSale) { - return; - } - await entriesBackend.updatePricing( - entry.id, - this.price, - this.availableForSale, - this.equityForSale - ); - this.clearStore(); - } - - async remove(entryId: string) { - await entriesBackend.remove(entryId); - } -} diff --git a/skyhitz-common/src/stores/index.ts b/skyhitz-common/src/stores/index.ts deleted file mode 100644 index 570df98..0000000 --- a/skyhitz-common/src/stores/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { SessionStore } from './session.store'; -import { SignUpValidationStore } from './sign-up-validation.store'; -import { SignInValidationStore } from './sign-in-validation.store'; -import { UsernameAndEmailValidationStore } from './username-and-email-validation.store'; -import { PlayerStore } from './player.store'; -import { InputSearchStore } from './input-search.store'; -import { EntriesSearchStore } from './entries-search.store'; -import { UsersSearchStore } from './users-search.store'; -import { ProfileStore } from './profile.store'; -import { EditProfileStore } from './edit-profile.store'; -import { LikesStore } from './likes.store'; -import { PaymentsStore } from './payments.store'; -import { EntryStore } from './entry.store'; -import { UserEntriesStore } from './user-entries.store'; -import { WalletConnectStore } from './wallet-connect.store'; - -export const sessionStore = new SessionStore(); -export const signUpValidationStore = new SignUpValidationStore(); -export const signInValidationStore = new SignInValidationStore(); -export const usernameAndEmailValidationStore = new UsernameAndEmailValidationStore(); -export const playerStore = new PlayerStore(); -export const inputSearchStore = new InputSearchStore(); -export const entriesSearchStore = new EntriesSearchStore( - inputSearchStore.query -); -export const usersSearchStore = new UsersSearchStore(inputSearchStore.query); -export const profileStore = new ProfileStore(); -export const editProfileStore = new EditProfileStore(sessionStore); -export const likesStore = new LikesStore( - playerStore.observables, - sessionStore.session -); -export const paymentsStore = new PaymentsStore(); -export const entryStore = new EntryStore(); -export const userEntriesStore = new UserEntriesStore(sessionStore); -export const walletConnectStore = new WalletConnectStore(); - -export type Stores = { - sessionStore: SessionStore; - signInValidationStore: SignInValidationStore; - signUpValidationStore: SignUpValidationStore; - usernameAndEmailValidationStore: UsernameAndEmailValidationStore; - playerStore: PlayerStore; - inputSearchStore: InputSearchStore; - entriesSearchStore: EntriesSearchStore; - usersSearchStore: UsersSearchStore; - profileStore: ProfileStore; - editProfileStore: EditProfileStore; - likesStore: LikesStore; - paymentsStore: PaymentsStore; - entryStore: EntryStore; - userEntriesStore: UserEntriesStore; - walletConnectStore: WalletConnectStore; -}; diff --git a/skyhitz-common/src/stores/input-search.store.ts b/skyhitz-common/src/stores/input-search.store.ts deleted file mode 100644 index adf28c9..0000000 --- a/skyhitz-common/src/stores/input-search.store.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { observable } from 'mobx'; - -export class InputSearchStore { - public query = observable({ - type: 'entries', - q: '', - }); - - constructor() {} - - public search(query: string) { - this.query.q = query; - } - - public updateSearchType(type: 'entries' | 'users') { - this.query.type = type; - } -} diff --git a/skyhitz-common/src/stores/likes.store.ts b/skyhitz-common/src/stores/likes.store.ts deleted file mode 100644 index bba4e22..0000000 --- a/skyhitz-common/src/stores/likes.store.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { observable, observe, IObservableObject } from 'mobx'; -import * as L from 'list'; -import { likesBackend } from '../backends/likes.backend'; -import { Entry, User } from '../models'; - -export class LikesStore { - @observable ids: Set = new Set([]); - @observable loading: boolean = false; - @observable loadingEntryLikes: boolean = false; - @observable entry!: Entry; - @observable entryLikes: L.List = L.from([]); - @observable entryLikesCount!: number; - @observable userLikes: L.List = L.from([]); - @observable userLikesCount!: number; - @observable user!: User; - - public viewLimit: number = 8; - disposer: any; - userDisposer: any; - - get hasMoreLikers(): boolean { - if (this.entryLikesCount > this.viewLimit) { - return true; - } - return false; - } - - get plusLikers() { - return this.kFormatter(this.entryLikesCount - this.viewLimit); - } - - kFormatter(num: number) { - return num > 999 ? (num / 1000).toFixed(1) + 'k' : num; - } - - constructor( - public observables: IObservableObject, - public session: IObservableObject - ) { - this.disposer = observe(observables, ({ object }) => { - if (!object.entry) { - return; - } - this.entry = object.entry; - this.refreshEntryLikes(this.entry.id); - }); - - this.userDisposer = observe(this.session, ({ object }) => { - this.user = object.user; - }); - } - - public clearLikes() { - this.entryLikes = L.from([]); - this.userLikes = L.from([]); - } - - public refreshEntryLikes(id: string) { - this.loadingEntryLikes = true; - likesBackend.entryLikes(id).then((payload) => { - if (payload) { - this.entryLikesCount = payload.count; - let users = payload.users.map( - (userPayload: any) => new User(userPayload) - ); - this.entryLikes = L.from(users); - } - - this.loadingEntryLikes = false; - }); - } - - public refreshLikes() { - this.loading = true; - likesBackend.userLikes().then((userLikes) => { - if (!userLikes) { - return; - } else { - let ids = userLikes.map((like: any) => like.id); - let entries = userLikes.map((like: any) => new Entry(like)); - this.ids = new Set(ids); - this.userLikes = L.from(entries); - this.userLikesCount = this.userLikes.length; - } - - this.loading = false; - }); - } - - async unlike(entry: Entry) { - this.ids.delete(entry.id); - let index = L.findIndex((like) => { - if (like) { - return like.id === entry.id; - } - return false; - }, this.userLikes); - this.userLikes = L.remove(index, 1, this.userLikes); - let unliked = await likesBackend.like(entry.id, false); - if (!unliked) { - this.ids = this.ids.add(entry.id); - this.userLikes = L.append(entry, this.userLikes); - } - this.userLikesCount = this.userLikes.length; - - let userIndex = L.findIndex((like) => { - if (like) { - return like.id === this.user.id; - } - return false; - }, this.entryLikes); - this.entryLikes = L.remove(userIndex, 1, this.entryLikes); - } - - async like(entry: Entry) { - this.ids = this.ids.add(entry.id); - this.userLikes = L.append(entry, this.userLikes); - let liked = await likesBackend.like(entry.id); - if (!liked) { - this.ids.delete(entry.id); - let index = L.findIndex((like) => { - if (like) { - return like.id === entry.id; - } - return false; - }, this.userLikes); - this.userLikes = L.remove(index, 1, this.userLikes); - } - this.userLikesCount = this.userLikes.length; - - this.entryLikes = L.append(this.user, this.entryLikes); - } - - public toggleLike(entry: Entry) { - if (this.isEntryLiked(entry)) { - return this.unlike(entry); - } - return this.like(entry); - } - - get isLiked() { - if (!this.entry) { - return false; - } - return this.ids.has(this.entry.id); - } - - isEntryLiked(entry: Entry) { - if (!entry) { - return false; - } - return this.ids.has(entry.id); - } -} diff --git a/skyhitz-common/src/stores/payments.store.ts b/skyhitz-common/src/stores/payments.store.ts deleted file mode 100644 index 6c0f29e..0000000 --- a/skyhitz-common/src/stores/payments.store.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { observable } from 'mobx'; -import { paymentsBackend } from '../backends/payments.backend'; -import { entriesBackend } from '../backends/entries.backend'; -import { Config } from '../config'; - -export class PaymentsStore { - @observable - subscribed: boolean = false; - @observable - subscriptionLoaded: boolean = false; - @observable - credits: number = 0; - @observable - submittingSubscription: boolean = false; - @observable - submittingWithdraw: boolean = false; - @observable - loadingBalance: boolean = false; - @observable - xlmPrice: number = 0; - - @observable - entryPrices: Map = new Map(); - - constructor() {} - - setLoadingBalance(loading: boolean) { - this.loadingBalance = loading; - } - - setSubmittingSubscription(submitting) { - this.submittingSubscription = submitting; - } - - async subscribeUser(cardToken: string) { - this.submittingSubscription = true; - await paymentsBackend.subscribe(cardToken); - this.submittingSubscription = false; - this.subscribed = true; - return true; - } - - async buyCredits(cardToken: string, amount: number) { - this.submittingSubscription = true; - await paymentsBackend.buyCredits(cardToken, amount); - this.submittingSubscription = false; - this.subscribed = true; - return true; - } - - async refreshSubscription() { - this.loadingBalance = true; - let { subscribed, credits } = await paymentsBackend.refreshSubscription(); - this.subscribed = subscribed; - this.credits = credits; - this.subscriptionLoaded = true; - this.loadingBalance = false; - } - - async withdrawToExternalWallet( - withdrawAddress: string, - creditsToWithdraw: number - ) { - this.submittingWithdraw = true; - await paymentsBackend.withdrawToExternalWallet( - withdrawAddress, - creditsToWithdraw - ); - await this.refreshSubscription(); - this.submittingWithdraw = false; - } - - public async buyEntry(id: string, amount: number, price: number) { - return await entriesBackend.buyEntry(id, amount, price); - } - - public getPriceInfo(id: string) { - return entriesBackend.getPriceInfo(id); - } - - get xlmPriceWithFees() { - return this.xlmPrice * 1.06; - } - - public async refreshXLMPrice() { - const price = await paymentsBackend.getXLMPrice(); - - this.xlmPrice = parseFloat(price); - } - - public async fetchPriceFromHorizon(code: string, issuer: string) { - let { asks } = await fetch( - `${Config.HORIZON_URL}/order_book?selling_asset_type=credit_alphanum12&selling_asset_code=${code}&selling_asset_issuer=${issuer}&buying_asset_type=native` - ).then((res: any) => res.json()); - - if (asks && asks[0]) { - let { price, amount }: { price: string; amount: string } = asks[0]; - return { price: parseFloat(price), amount: parseFloat(amount) }; - } - - return null; - } - - public async fetchAndCachePrice(code: string, issuer: string) { - const identifier = `${code}-${issuer}`; - const val = this.entryPrices.get(identifier); - if (val) { - return val; - } - const newval = await this.fetchPriceFromHorizon(code, issuer); - if (newval) { - this.entryPrices.set(identifier, newval); - return newval; - } - return { price: 0, amount: 0 }; - } -} diff --git a/skyhitz-common/src/stores/player.store.ts b/skyhitz-common/src/stores/player.store.ts deleted file mode 100644 index f62b839..0000000 --- a/skyhitz-common/src/stores/player.store.ts +++ /dev/null @@ -1,535 +0,0 @@ -import { observable, computed, action } from 'mobx'; -import { Entry } from '../models'; -import * as L from 'list'; -import { entriesBackend } from '../backends/entries.backend'; -import { PlaybackState, SeekState, ControlsState } from '../types/index'; -import { Platform } from 'react-native'; - -export class PlayerStore { - constructor() {} - - public observables: any = observable({ - entry: null, - }); - @computed - get entry(): any { - return this.observables.entry; - } - - @observable - showMiniPlayer: boolean = false; - - @observable - show: boolean = false; - @observable - tabBarBottomPosition: number = 0; - @observable - loop: boolean = false; - @observable - shuffle: boolean = false; - @observable - playbackState: PlaybackState = 'LOADING'; - @observable - seekState: SeekState = 'NOT_SEEKING'; - @observable - controlsState: ControlsState = 'SHOWN'; - @observable - shouldPlay: boolean = false; - @observable - isOnFullScreenMode: boolean = false; - @observable - positionMillis: number = 0; - @observable - playbackInstancePosition: number = 0; - @observable - playbackInstanceDuration: number = 0; - @observable - lastPlaybackStateUpdate: number = Date.now(); - @observable - error: any; - @observable - networkState: any; - @observable - shouldPlayAtEndOfSeek: boolean = false; - @observable - sliderWidth: number = 0; - @observable - cueList: L.List = L.from([]); - @observable - currentIndex: number = 0; - @observable - retryTimes: number = 0; - @observable - playlistMode: boolean = false; - @observable - playbackInstance: any; - - @observable - seekPosition: number = 0; - - @observable - sliding: boolean = false; - - @observable - streamUrl: string = ''; - - video: any; - - @action - setSliding(sliding) { - this.sliding = sliding; - } - - mountVideo = (component) => { - if (!component) return; - - this.video = component; - this.loadNewPlaybackInstance(false); - }; - - async loadNewPlaybackInstance(playing, streamUrl = this.streamUrl) { - if (this.playbackInstance != null) { - try { - await this.playbackInstance.unloadAsync(); - } catch (e) {} - - this.playbackInstance = null; - } - - if (!streamUrl) return; - if (!this.video) return; - - await this.video.loadAsync( - { uri: streamUrl }, - { - shouldPlay: playing, - positionMillis: 0, - progressUpdateIntervalMillis: 50, - } - ); - this.playbackInstance = this.video; - if (playing && !this.isPlaying) { - this.playAsync(); - } - } - - @action - async refreshEntry() { - if (this.entry && this.entry.id) { - let entry = await entriesBackend.getById(this.entry.id); - this.observables.entry = entry; - } - } - - setPlaylistMode(entries: L.List) { - this.playlistMode = true; - this.cueList = entries; - } - - setPlaylistModeFromArray(entries: Entry[]) { - this.playlistMode = true; - this.cueList = L.from(entries); - } - - disablePlaylistMode() { - this.playlistMode = false; - this.cueList = L.from([]); - } - - setPlaybackInstance(playbackInstance: any) { - if (playbackInstance !== null) { - this.playbackInstance = playbackInstance; - } - } - - @computed - get playbackInstanceExists() { - return !!this.playbackInstance; - } - - async playAsync() { - if (this.playbackInstanceExists) { - this.setPlaybackState('PLAYING'); - return await this.playbackInstance.setStatusAsync({ shouldPlay: true }); - } - } - - async pauseAsync() { - if (this.playbackInstanceExists) { - this.setPlaybackState('PAUSED'); - await this.playbackInstance.setStatusAsync({ shouldPlay: false }); - this.setPlaybackState('PAUSED'); - return true; - } - } - - async stopAsync() { - if (this.playbackInstanceExists) { - this.setPlaybackState('PAUSED'); - return await this.playbackInstance.setStatusAsync({ - shouldPlay: false, - positionMillis: 0, - }); - } - } - - async toggleLoop() { - if (this.playbackInstanceExists) { - this.loop = !this.loop; - return await this.playbackInstance.setIsLoopingAsync(this.loop); - } - } - - async presentFullscreenPlayer() { - if (this.playbackInstance) { - await this.playbackInstance.presentFullscreenPlayer(); - return; - } - } - - async dismissFullscreenPlayer() { - if (this.playbackInstance) { - await this.playbackInstance.dismissFullscreenPlayer(); - return; - } - } - - onFullscreenUpdate(status: any) { - if (status.fullscreenUpdate === 1) { - this.isOnFullScreenMode = true; - } - - if (status.fullscreenUpdate === 3) { - this.isOnFullScreenMode = false; - // resume video manually, - // TODO: add bug to expo client on github. - if (this.shouldPlay) { - this.playAsync(); - } - } - } - - async togglePlay() { - if (this.isPlaying) { - return this.pauseAsync(); - } - return this.playAsync(); - } - - async replay() { - await this.stopAsync(); - this.setPlaybackState('PLAYING'); - return this.playAsync(); - } - - @computed - get isPlaying() { - if (this.playbackState === 'PLAYING') { - return true; - } - return false; - } - - async loadAndPlay(entry: Entry, play = true) { - if (!entry) { - return null; - } - const currentIndex = L.findIndex( - (item) => !!item && item.id === entry.id, - this.cueList - ); - if (currentIndex !== -1) { - this.currentIndex = currentIndex; - } - - this.setPlaybackState('LOADING'); - this.observables.entry = entry; - this.showPlayer(); - let { videoUrl, isIpfs, videoSrc } = entry; - - if (!videoUrl) { - return; - } - - this.streamUrl = isIpfs && videoSrc ? videoSrc : videoUrl; - await this.loadNewPlaybackInstance(play, this.streamUrl); - this.setPlaybackState(play ? 'PLAYING' : 'PAUSED'); - return; - } - - async playNext() { - this.setPlaybackState('LOADING'); - this.pauseAsync(); - - if (this.isCurrentIndexAtTheEndOfCue) { - // Override the value if playlistMode was set to true, it will loop through the - // list instead of playing a related video. - if (this.playlistMode) { - let entry = L.nth(0, this.cueList); - this.currentIndex = 0; - if (!entry) return; - return this.loadAndPlay(entry); - } - } - - this.currentIndex++; - let nextEntry = L.nth(this.currentIndex, this.cueList); - if (!nextEntry) return; - this.loadAndPlay(nextEntry); - } - - async loadPlayAndPushToCueList(entry: Entry) { - this.loadAndPlay(entry); - this.cueList = L.append(this.entry, this.cueList); - this.currentIndex = this.cueList.length - 1; - } - - async loadPlayAndUnshiftToCueList(entry: Entry) { - this.loadAndPlay(entry); - this.cueList = L.prepend(this.entry, this.cueList); - this.currentIndex = 0; - } - - onError(e: string) { - console.info(e); - } - - @action - toggleShuffle() { - this.shuffle = !this.shuffle; - } - - @action - unmountMiniPlayer() { - this.showMiniPlayer = false; - } - - @action - mountMiniPlayer() { - this.showMiniPlayer = true; - } - - @action - hidePlayer() { - this.show = false; - } - - @action - showPlayer() { - this.show = true; - } - - @computed - get isCurrentIndexAtTheStartOfCue() { - return this.currentIndex === 0; - } - - @computed - get isCurrentIndexAtTheEndOfCue() { - return this.currentIndex === this.cueList.length - 1; - } - - @action - updateTabBarBottomPosition(bottom: number) { - this.tabBarBottomPosition = bottom; - } - - async playPrev() { - this.setPlaybackState('LOADING'); - this.pauseAsync(); - if (this.isCurrentIndexAtTheStartOfCue) { - // Override the value if playlistMode was set to true, it will loop through the - // list instead of playing a related video. - if (this.playlistMode) { - let lastIndexInCueList = this.cueList.length - 1; - let entry = L.nth(lastIndexInCueList, this.cueList); - this.currentIndex = lastIndexInCueList; - if (!entry) return; - return this.loadAndPlay(entry); - } - - return; - } - - this.currentIndex--; - let prevEntry = L.nth(this.currentIndex, this.cueList); - if (!prevEntry) return; - this.loadAndPlay(prevEntry); - } - - padWithZero = (value: number) => { - const result = value.toString(); - if (value < 10) { - return '0' + result; - } - return result; - }; - - getMMSSFromMillis(millis: number) { - const totalSeconds = millis / 1000; - const seconds = Math.floor(totalSeconds % 60); - const minutes = Math.floor(totalSeconds / 60); - return this.padWithZero(minutes) + ':' + this.padWithZero(seconds); - } - - @computed - get durationDisplay() { - return this.getMMSSFromMillis(this.playbackInstanceDuration); - } - - @computed - get positionDisplay() { - return this.getMMSSFromMillis(this.playbackInstancePosition); - } - - @action - setSeekState(seekState: SeekState) { - this.seekState = seekState; - } - - onSeekSliderValueChange = () => { - if ( - this.playbackInstance !== null && - this.seekState !== 'SEEKING' && - this.seekState !== 'SEEKED' - ) { - this.shouldPlayAtEndOfSeek = false; - this.setSeekState('SEEKING'); - - if (this.isPlaying) { - this.pauseAsync(); - this.shouldPlayAtEndOfSeek = true; - } - } - }; - - onSeekSliderSlidingComplete = async (value: number) => { - if (this.seekState !== 'SEEKED') { - this.setSeekState('SEEKED'); - let status; - try { - status = await this.playbackInstance.setStatusAsync({ - positionMillis: value * this.playbackInstanceDuration, - shouldPlay: this.shouldPlayAtEndOfSeek, - }); - - this.setSeekState('NOT_SEEKING'); - this.setPlaybackState(this.getPlaybackStateFromStatus(status)); - } catch (message) {} - } - }; - - onSeekBarTap = (evt: any) => { - if (this.sliding) return; - if ( - !( - this.playbackState === 'LOADING' || - this.playbackState === 'ENDED' || - this.playbackState === 'ERROR' || - this.controlsState !== 'SHOWN' - ) - ) { - let xValue; - if (Platform.OS === 'web') { - xValue = evt.nativeEvent.clientX - evt.target.getBoundingClientRect().x; - } else { - xValue = evt.nativeEvent.locationX; - } - const value = xValue / this.sliderWidth; - this.onSeekSliderSlidingComplete(value); - } - }; - - onSliderLayout = (evt: any) => { - this.sliderWidth = evt.nativeEvent.layout.width; - }; - - setNetworkState(state: any) { - this.networkState = state; - } - - generateRandomNumber(max: number): number { - var num = Math.floor(Math.random() * (max + 1)); - return num === this.currentIndex ? this.generateRandomNumber(max) : num; - } - - async handleEndedPlaybackState() { - if (this.playbackState === 'ENDED') { - let pause = await this.pauseAsync(); - if (pause) { - if (this.shuffle) { - this.currentIndex = this.generateRandomNumber(this.cueList.length); - } - return this.playNext(); - } - } - } - - @computed - get disablePlaybackStatusUpdate(): boolean { - if ( - this.playbackState === 'ENDED' || - this.playbackState === 'LOADING' || - this.seekState === 'SEEKING' || - this.seekState === 'SEEKED' - ) { - return true; - } - return false; - } - - @action - onPlaybackStatusUpdate(status: any) { - if (!status.isLoaded) { - if (status.error) { - const errorMsg = `Encountered a fatal error during playback: ${status.error}`; - this.error = errorMsg; - return this.setPlaybackState('ERROR'); - } - return; - } - - if (this.networkState === 'none' && status.isBuffering) { - this.setPlaybackState('ERROR'); - this.error = - 'You are probably offline. Please make sure you are connected to the Internet to watch this video'; - return; - } - - if (status.isPlaying && !status.isBuffering) { - this.playbackInstancePosition = status.positionMillis; - this.playbackInstanceDuration = status.durationMillis; - this.seekPosition = - this.playbackInstancePosition / this.playbackInstanceDuration; - } - - this.shouldPlay = status.shouldPlay; - - this.setPlaybackState(this.getPlaybackStateFromStatus(status)); - } - - @action - async setPlaybackState(playbackState: PlaybackState) { - if (this.playbackState !== playbackState) { - this.playbackState = playbackState; - this.handleEndedPlaybackState(); - this.lastPlaybackStateUpdate = Date.now(); - } - } - - getPlaybackStateFromStatus = (status: any) => { - if (status.didJustFinish && !status.isLooping) { - return 'ENDED'; - } - - if (status.isPlaying) { - return 'PLAYING'; - } - - if (status.isBuffering) { - return 'BUFFERING'; - } - - return 'PAUSED'; - }; -} diff --git a/skyhitz-common/src/stores/profile.store.ts b/skyhitz-common/src/stores/profile.store.ts deleted file mode 100644 index f708bca..0000000 --- a/skyhitz-common/src/stores/profile.store.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { observable } from 'mobx'; -import { User, Entry } from '../models'; -import * as L from 'list'; -import { entriesBackend } from '../backends/entries.backend'; - -export class ProfileStore { - @observable - user!: User; - @observable entries: L.List = L.from([]); - @observable loadingEntries: boolean = false; - constructor() {} - - public async getProfileInfo(user: User) { - if (!user) return; - this.user = user; - return this.getUserEntries(user.id as string); - } - - public async getUserEntries(userId: string) { - this.loadingEntries = true; - this.entries = L.from([]); - const entries = await entriesBackend.getByUserId(userId); - this.loadingEntries = false; - return this.setEntries(L.from(entries)); - } - - public setEntries(entries: L.List) { - return (this.entries = entries); - } -} diff --git a/skyhitz-common/src/stores/session.store.ts b/skyhitz-common/src/stores/session.store.ts deleted file mode 100644 index 756e061..0000000 --- a/skyhitz-common/src/stores/session.store.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { observable, observe, computed, IObservableObject } from 'mobx'; -import { User } from '../models'; -import { userBackend } from '../backends/user.backend'; -import { forceSignOut } from '../backends/apollo-client.backend'; -import { SignUpForm } from '../types'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { userDataKey } from '../constants/constants'; - -export class SessionStore { - public session: { user: User | null } & IObservableObject = observable({ - user: null, - }); - @computed - get user() { - return this.session.user; - } - constructor() {} - - async signUp(signUp: SignUpForm) { - let userPayload = await userBackend.signUp(signUp); - await this.setUser(userPayload); - return (this.session.user = new User(userPayload)); - } - - async requestToken(usernameOrEmail: string, publicKey: string) { - await userBackend.requestToken(usernameOrEmail, publicKey); - return; - } - - async signIn(token?: string, uid?: string, xdr = '') { - let userPayload = await userBackend.signIn(token, uid, xdr); - if (userPayload) { - await this.setUser(userPayload); - return (this.session.user = new User(userPayload)); - } - return null; - } - - async setUser(value: any) { - value = JSON.stringify(value); - if (value) return AsyncStorage.setItem(userDataKey, value); - else console.info('not set, stringify failed:', userDataKey, value); - } - - public forceSignOutDisposer = observe(forceSignOut, ({ object }) => { - if (object.value) { - this.signOut(); - } - }); - - async signOut() { - this.session.user = null; - return await AsyncStorage.removeItem(userDataKey); - } - - async loadSession() { - return await this.loadFromStorage(); - } - - async loadFromStorage() { - try { - let userPayload = await AsyncStorage.getItem(userDataKey); - if (userPayload) { - this.session.user = new User(JSON.parse(userPayload)); - return this.session.user; - } - } catch (e) { - console.info(e); - } - - return await this.signOut(); - } - - async refreshUser() { - if (!this.session.user) { - return await this.signOut(); - } - try { - let userPayload = await userBackend.getAuthenticatedUser(); - if (userPayload) { - let userPayloadClone = Object.assign({}, userPayload); - userPayloadClone.jwt = this.session.user.jwt; - await this.setUser(userPayloadClone); - this.session.user = new User(userPayloadClone); - return this.session.user; - } - } catch (e) { - console.info(e); - } - return await this.signOut(); - } -} diff --git a/skyhitz-common/src/stores/sign-in-validation.store.ts b/skyhitz-common/src/stores/sign-in-validation.store.ts deleted file mode 100644 index 89bbeb0..0000000 --- a/skyhitz-common/src/stores/sign-in-validation.store.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { observable, computed } from 'mobx'; - -export class SignInValidationStore { - @observable - usernameOrEmailError!: string; - @observable - usernameOrEmailValid!: boolean; - @observable - backendError!: string; - - constructor() {} - - validateUsernameOrEmail(usernameOrEmail: string) { - if (!usernameOrEmail) { - this.usernameOrEmailValid = false; - this.usernameOrEmailError = 'Username is required.'; - return; - } - - if (usernameOrEmail.length < 2) { - this.usernameOrEmailValid = false; - this.usernameOrEmailError = 'Enter a valid username or email.'; - return; - } - - this.usernameOrEmailError = ''; - return (this.usernameOrEmailValid = true); - } - - @computed - get error() { - if (this.usernameOrEmailError) { - return this.usernameOrEmailError; - } - - if (this.backendError) { - return this.backendError; - } - - return null; - } - - @computed - get validForm() { - return this.usernameOrEmailValid; - } - - setBackendError(error: string) { - this.backendError = error; - } -} diff --git a/skyhitz-common/src/stores/sign-up-validation.store.ts b/skyhitz-common/src/stores/sign-up-validation.store.ts deleted file mode 100644 index e88223d..0000000 --- a/skyhitz-common/src/stores/sign-up-validation.store.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { observable, computed } from 'mobx'; - -export class SignUpValidationStore { - @observable usernameError: string = ''; - @observable usernameValid; - @observable displayNameError: string = ''; - @observable displayNameValid; - @observable emailError: string = ''; - @observable emailValid; - @observable backendError: string = ''; - - constructor() {} - - validateUsername(username: string) { - if (!username) { - this.usernameValid = false; - this.usernameError = 'Username is required.'; - return; - } - - if (username.length < 2) { - this.usernameValid = false; - this.usernameError = 'Username is minimum 2 characters.'; - return; - } - - let validRegex = /^[a-zA-Z0-9_-]+$/.test(username); - if (!validRegex) { - this.usernameValid = false; - this.usernameError = - 'Usernames cannot have spaces or special characters.'; - return; - } - - this.usernameError = ''; - return (this.usernameValid = true); - } - - validateDisplayName(displayName: string) { - if (!displayName) { - this.displayNameValid = false; - this.displayNameError = 'Display name is required.'; - return; - } - - if (displayName.length < 2) { - this.displayNameValid = false; - this.displayNameError = 'Display name is minimum 2 characters.'; - return; - } - - this.displayNameError = ''; - return (this.displayNameValid = true); - } - - validateEmail(email: string) { - if (!email) { - this.emailValid = false; - this.emailError = 'Email is required.'; - return; - } - - var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - if (!re.test(email)) { - this.emailValid = false; - this.emailError = 'Please enter a valid email.'; - return; - } - - this.emailError = ''; - return (this.emailValid = true); - } - - @computed - get error() { - if (this.usernameError) { - return this.usernameError; - } - - if (this.displayNameError) { - return this.displayNameError; - } - - if (this.emailError) { - return this.emailError; - } - - if (this.backendError) { - return this.backendError; - } - - return null; - } - - @computed - get validForm() { - return this.usernameValid && this.displayNameValid && this.emailValid; - } - - setBackendError(error: string) { - this.backendError = error; - } -} diff --git a/skyhitz-common/src/stores/user-entries.store.ts b/skyhitz-common/src/stores/user-entries.store.ts deleted file mode 100644 index 1681ef8..0000000 --- a/skyhitz-common/src/stores/user-entries.store.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { observable } from 'mobx'; -import * as L from 'list'; -import { entriesBackend } from '../backends/entries.backend'; -import { Entry } from '../models'; -import { SessionStore } from './session.store'; - -export class UserEntriesStore { - @observable entries: L.List = L.from([]); - @observable loading: boolean = false; - - constructor(private sessionStore: SessionStore) {} - - public async refreshEntries() { - if (!this.sessionStore.user) { - return; - } - if (!this.sessionStore.user.id) { - return; - } - this.loading = true; - - const entries = await entriesBackend.getByUserId(this.sessionStore.user.id); - this.loading = false; - this.entries = L.from(entries ? entries : []); - } - - get entriesCount() { - return this.entries ? this.entries.length : 0; - } -} diff --git a/skyhitz-common/src/stores/username-and-email-validation.store.ts b/skyhitz-common/src/stores/username-and-email-validation.store.ts deleted file mode 100644 index 457366c..0000000 --- a/skyhitz-common/src/stores/username-and-email-validation.store.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { observable, computed } from 'mobx'; - -export class UsernameAndEmailValidationStore { - @observable usernameError: string = ''; - @observable usernameValid: boolean = true; - @observable emailError: string = ''; - @observable emailValid: boolean = true; - @observable backendError: string = ''; - - constructor() {} - - validateUsername(username: string) { - if (!username) { - this.usernameValid = false; - this.usernameError = 'Username is required.'; - return; - } - - if (username.length < 2) { - this.usernameValid = false; - this.usernameError = 'Username is minimum 2 characters.'; - return; - } - - let validRegex = /^[a-zA-Z0-9_-]+$/.test(username); - if (!validRegex) { - this.usernameValid = false; - this.usernameError = - 'Usernames cannot have spaces or special characters.'; - return; - } - - this.usernameError = ''; - return (this.usernameValid = true); - } - - validateEmail(email: string) { - if (!email) { - this.emailValid = false; - this.emailError = 'Email is required.'; - return; - } - - var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - if (!re.test(email)) { - this.emailValid = false; - this.emailError = 'Please enter a valid email.'; - return; - } - - this.emailError = ''; - return (this.emailValid = true); - } - - @computed - get error() { - if (this.usernameError) { - return this.usernameError; - } - - if (this.emailError) { - return this.emailError; - } - - if (this.backendError) { - return this.backendError; - } - - return null; - } - - @computed - get validForm() { - return this.usernameValid && this.emailValid; - } - - setBackendError(error: string) { - this.backendError = error; - } -} diff --git a/skyhitz-common/src/stores/users-search.store.ts b/skyhitz-common/src/stores/users-search.store.ts deleted file mode 100644 index 7fd4aa1..0000000 --- a/skyhitz-common/src/stores/users-search.store.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { User } from '../models/user.model'; -import { usersBackend } from '../backends/users.backend'; -import { observable, observe, computed } from 'mobx'; -import * as L from 'list'; -const debounce = require('lodash.debounce'); - -export class UsersSearchStore { - @observable public users: L.List = L.from([]); - @observable public recentSearches: L.List = L.from([]); - @observable public topSearches: L.List = L.from([]); - @observable searching: boolean = false; - @observable loadingRecentSearches: boolean = false; - @observable loadingTopSearches: boolean = false; - @observable query: string = ''; - disposer: any; - @computed get active() { - return !!this.query; - } - - constructor(public queryObservable: any) { - this.disposer = observe(queryObservable, ({ object }) => { - if (object.type === 'users' && object.q !== this.query) { - this.query = object.q; - this.searching = true; - this.searchUsers(object.q); - } - }); - } - - public searchUsers(q: string) { - usersBackend.search(q).then((users) => { - this.setUsers(L.from(users)); - this.searching = false; - }); - } - - public debouncedSearch = debounce(this.searchUsers, 400); - - public setUsers(users: L.List) { - this.users = users; - } - - public setRecentSearches(users: L.List) { - this.recentSearches = users; - } - - public setTopSearches(users: L.List) { - this.topSearches = users; - } - - public getRecentSearches() { - this.loadingRecentSearches = true; - return usersBackend.getRecentSearches().then((users) => { - this.setRecentSearches(L.from(users)); - this.loadingRecentSearches = false; - }); - } - - public getTopSearches() { - this.loadingTopSearches = true; - return usersBackend.getTopSearches().then((users) => { - this.setTopSearches(L.from(users)); - this.loadingTopSearches = false; - }); - } -} diff --git a/skyhitz-common/src/stores/wallet-connect.store.ts b/skyhitz-common/src/stores/wallet-connect.store.ts deleted file mode 100644 index 5c6d07b..0000000 --- a/skyhitz-common/src/stores/wallet-connect.store.ts +++ /dev/null @@ -1,164 +0,0 @@ -import WalletConnect, { CLIENT_EVENTS } from '@walletconnect/client'; - -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { observable } from 'mobx'; - -const stellarMeta = { - chainName: 'stellar:pubnet', - methods: ['stellar_signAndSubmitXDR', 'stellar_signXDR'], -}; - -export class WalletConnectStore { - client: null | WalletConnect; - session: any; - proposals: Map; - @observable uri: null | string; - @observable state: - | 'disconnected' - | 'paring-proposal' - | 'paring-created' - | 'session-proposal' - | 'session-created'; - @observable publicKey: string = ''; - sessions: any; - - constructor() { - this.state = 'disconnected'; - this.client = null; - this.session = null; - this.uri = null; - this.proposals = new Map(); - WalletConnect.init({ - projectId: '422a527ddc3ed4c5fff60954fcc8ed83', - metadata: { - name: 'Skyhitz', - description: 'Skyhitz', - url: 'https://skyhitz.io', - icons: ['https://skyhitz.io/img/icon-512.png'], - }, - storageOptions: { - asyncStorage: AsyncStorage as any, - }, - }) - .then(async (result) => { - this.client = result; - const itemsStored = await this.client?.storage.keyValueStorage.getItem( - 'wc@2:client:0.3//session:settled' - ); - if (itemsStored && itemsStored.length) { - const [session] = itemsStored; - this.setSession(session); - } - - this.subscribeToEvents(); - }) - .catch((e) => { - console.log(e); - }); - } - - clearState() { - this.state = 'disconnected'; - this.publicKey = ''; - this.session = null; - return AsyncStorage.clear(); - } - - setSession(session) { - this.session = session; - const { state } = session; - const [stellarAccount] = state.accounts; - this.publicKey = stellarAccount.replace(`${stellarMeta.chainName}:`, ''); - this.state = 'session-created'; - return this.publicKey; - } - - async connect() { - try { - return this.setSession( - await this.client?.connect({ - permissions: { - blockchain: { - chains: [stellarMeta.chainName], - }, - jsonrpc: { - methods: stellarMeta.methods, - }, - }, - }) - ); - } catch (e) { - console.log('catched error on reject:', e); - this.state = 'disconnected'; - } - - return this.publicKey; - } - - async disconnect() { - await this.client?.disconnect({ - topic: this.session.topic, - reason: { - code: 1, - message: 'Logged out', - }, - }); - await this.clearState(); - } - - subscribeToEvents() { - console.log('subscribed to events'); - this.client?.on(CLIENT_EVENTS.pairing.proposal, async (proposal) => { - const { uri } = proposal.signal.params; - console.log('pairing proposal'); - this.uri = uri; - this.state = 'paring-proposal'; - }); - - this.client?.on(CLIENT_EVENTS.pairing.created, async (proposal) => { - this.uri = null; - this.state = 'paring-created'; - }); - - this.client?.on(CLIENT_EVENTS.session.proposal, async (proposal) => { - this.state = 'session-proposal'; - }); - - this.client?.on(CLIENT_EVENTS.session.created, async (proposal) => { - this.state = 'session-created'; - }); - - this.client?.on(CLIENT_EVENTS.session.deleted, (session) => { - console.log(session); - this.clearState(); - }); - } - - signXdr(xdr) { - return this.client?.request({ - topic: this.session.topic, - chainId: stellarMeta.chainName, - request: { - jsonrpc: '2.0', - method: 'stellar_signXDR', - params: { - xdr, - }, - } as any, - }); - } - - signAndSubmitXdr(xdr) { - return this.client?.request({ - topic: this.session.topic, - chainId: stellarMeta.chainName, - request: { - jsonrpc: '2.0', - method: 'stellar_signAndSubmitXDR', - params: { - xdr, - }, - } as any, - }); - } -} diff --git a/skyhitz-common/src/utils/unique-id-generator.ts b/skyhitz-common/src/utils/unique-id-generator.ts deleted file mode 100644 index d63481d..0000000 --- a/skyhitz-common/src/utils/unique-id-generator.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Fancy ID generator that creates 20-character string identifiers with the following properties: - * - * 1. They're based on timestamp so that they sort *after* any existing ids. - * 2. They contain 72-bits of random data after the timestamp so that IDs won't collide with other clients' IDs. - * 3. They sort *lexicographically* (so the timestamp is converted to characters that will sort properly). - * 4. They're monotonically increasing. Even if you generate more than one in the same timestamp, the - * latter ones will sort after the former ones. We do this by using the previous random bits - * but "incrementing" them by 1 (only in the case of a timestamp collision). - */ - -export default class UniqueIdGenerator { - // Timestamp of last push, used to prevent local collisions if you push twice in one ms. - private static lastPushTime = 0; - - // Modeled after base64 web-safe chars, but ordered by ASCII. - private static PUSH_CHARS = - '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'; - - // We generate 72-bits of randomness which get turned into 12 characters and appended to the - // timestamp to prevent collisions with other clients. We store the last characters we - // generated because in the event of a collision, we'll use those same characters except - // "incremented" by one. - private static lastRandChars: any = []; - - // Generates chronologically orderable unique string one by one - public static generate() { - var now = new Date().getTime(); - var duplicateTime = now === UniqueIdGenerator.lastPushTime; - UniqueIdGenerator.lastPushTime = now; - - var timeStampChars = new Array(8); - for (var i = 7; i >= 0; i--) { - timeStampChars[i] = UniqueIdGenerator.PUSH_CHARS.charAt(now % 64); - // NOTE: Can't use << here because javascript will convert to int and lose the upper bits. - now = Math.floor(now / 64); - } - if (now !== 0) - throw new Error('We should have converted the entire timestamp.'); - - var id = timeStampChars.join(''); - - if (!duplicateTime) { - for (i = 0; i < 12; i++) { - UniqueIdGenerator.lastRandChars[i] = Math.floor(Math.random() * 64); - } - } else { - // If the timestamp hasn't changed since last push, use the same random number, except incremented by 1. - for (i = 11; i >= 0 && UniqueIdGenerator.lastRandChars[i] === 63; i--) { - UniqueIdGenerator.lastRandChars[i] = 0; - } - UniqueIdGenerator.lastRandChars[i]++; - } - for (i = 0; i < 12; i++) { - id += UniqueIdGenerator.PUSH_CHARS.charAt( - UniqueIdGenerator.lastRandChars[i] - ); - } - if (id.length !== 20) throw new Error('Length should be 20.'); - - return id; - } -} diff --git a/modules/accounts/AuthScreen.tsx b/src/accounts/AuthScreen.tsx similarity index 92% rename from modules/accounts/AuthScreen.tsx rename to src/accounts/AuthScreen.tsx index 79cbcd6..28f8797 100644 --- a/modules/accounts/AuthScreen.tsx +++ b/src/accounts/AuthScreen.tsx @@ -1,14 +1,13 @@ import React from 'react'; import { StyleSheet, View, Text, TouchableHighlight } from 'react-native'; -import Layout from 'app/constants/Layout'; -import TextWithLetterSpacing from 'app/modules/ui/TextWithLetterSpacing'; -import Colors from 'app/constants/Colors'; -import { NavStatelessComponent } from 'app/interfaces/Interfaces'; +import Layout from 'app/src/constants/Layout'; +import TextWithLetterSpacing from 'app/src/ui/TextWithLetterSpacing'; +import Colors from 'app/src/constants/Colors'; import { useLinkTo } from '@react-navigation/native'; -import BackgroundImage from 'app/modules/ui/BackgroundImage'; +import BackgroundImage from 'app/src/ui/BackgroundImage'; import SkyhitzLogo from '../marketing/web/SkyhitzLogo'; -const AuthScreen: NavStatelessComponent = (props) => { +const AuthScreen = (props) => { const linkTo = useLinkTo(); return ( diff --git a/modules/accounts/LoadingScreen.tsx b/src/accounts/LoadingScreen.tsx similarity index 89% rename from modules/accounts/LoadingScreen.tsx rename to src/accounts/LoadingScreen.tsx index 4cb38ba..5f75897 100644 --- a/modules/accounts/LoadingScreen.tsx +++ b/src/accounts/LoadingScreen.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ActivityIndicator, StyleSheet, View } from 'react-native'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; export default () => { return ( diff --git a/modules/accounts/OpenEmail.ts b/src/accounts/OpenEmail.ts similarity index 100% rename from modules/accounts/OpenEmail.ts rename to src/accounts/OpenEmail.ts diff --git a/modules/accounts/SignInScreen.tsx b/src/accounts/SignInScreen.tsx similarity index 77% rename from modules/accounts/SignInScreen.tsx rename to src/accounts/SignInScreen.tsx index 1b46a92..f1a8e54 100644 --- a/modules/accounts/SignInScreen.tsx +++ b/src/accounts/SignInScreen.tsx @@ -1,6 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { observer } from 'mobx-react'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; import { StyleSheet, View, @@ -10,35 +9,49 @@ import { ActivityIndicator, Platform, } from 'react-native'; -import { Stores } from 'app/functions/Stores'; import { useLinkTo } from '@react-navigation/native'; -import BackgroundImage from 'app/modules/ui/BackgroundImage'; -import cursorPointer from 'app/constants/CursorPointer'; +import BackgroundImage from 'app/src/ui/BackgroundImage'; +import cursorPointer from 'app/src/constants/CursorPointer'; import { openEmail } from './OpenEmail'; import * as Linking from 'expo-linking'; -import { Config } from 'app/skyhitz-common/src/config'; +import { Config } from 'app/src/config'; import * as Device from 'expo-device'; -import WalletConnectBtn from 'app/modules/accounts/WalletConnectBtn'; +import WalletConnectBtn from 'app/src/accounts/WalletConnectBtn'; import tw from 'twin.macro'; +import { SessionStore } from '../stores/session'; +import { + usernameOrEmailValidationErrorAtom, + usernameOrEmailBackendErrorAtom, + usernameOrEmailErrorAtom, + usernameOrEmailValidAtom, +} from 'app/src/atoms/atoms'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; + +export default ({ route, navigation }) => { + const { requestToken, signIn } = SessionStore(); + const setValidationError = useSetRecoilState( + usernameOrEmailValidationErrorAtom + ); + const setBackendError = useSetRecoilState(usernameOrEmailBackendErrorAtom); + const error = useRecoilValue(usernameOrEmailErrorAtom); + const validForm = useRecoilValue(usernameOrEmailValidAtom); -export default observer(({ route, navigation }) => { - const { signInValidationStore, sessionStore } = Stores(); const [usernameOrEmail, setUsernameOrEmail] = useState(''); const [loading, setLoading] = useState(false); const [showEmailLink, setShowEmailLink] = useState(false); const { token, uid } = route.params || {}; const linkTo = useLinkTo(); - const signIn = async () => { + const handleSignIn = async () => { setLoading(true); try { - await sessionStore.requestToken(usernameOrEmail, ''); + await requestToken(usernameOrEmail, ''); // check your email to access your account setLoading(false); setShowEmailLink(true); return; } catch (e) { - signInValidationStore.setBackendError(e as any); + setBackendError(e as any); } return setLoading(false); }; @@ -46,12 +59,12 @@ export default observer(({ route, navigation }) => { const signInWithXDR = async (xdr) => { setLoading(true); try { - await sessionStore.signIn(undefined, undefined, xdr); + await signIn(undefined, undefined, xdr); // check your email to access your account setLoading(false); return; } catch (e) { - signInValidationStore.setBackendError(e as any); + setBackendError(e as any); linkTo('/accounts/sign-up'); } return setLoading(false); @@ -61,9 +74,23 @@ export default observer(({ route, navigation }) => { openEmail(); }; + const validateUsernameOrEmail = (usernameOrEmail: string) => { + if (!usernameOrEmail) { + setValidationError('Username is required.'); + return; + } + + if (usernameOrEmail.length < 2) { + setValidationError('Enter a valid username or email.'); + return; + } + + setValidationError(''); + }; + const updateUsernameOrEmail = ({ target }: any) => { setUsernameOrEmail(target.value); - signInValidationStore.validateUsernameOrEmail(target.value); + validateUsernameOrEmail(target.value); }; const onSubmit = (e) => { @@ -86,7 +113,7 @@ export default observer(({ route, navigation }) => { ); return; } - const res = await sessionStore.signIn(token, uid); + const res = await signIn(token, uid); if (res) { return linkTo('/'); } @@ -168,23 +195,15 @@ export default observer(({ route, navigation }) => { /> - - {signInValidationStore.error} + + {error} {loading ? ( { )} ); -}); +}; let styles = StyleSheet.create({ line: { diff --git a/modules/accounts/SignUpScreen.tsx b/src/accounts/SignUpScreen.tsx similarity index 60% rename from modules/accounts/SignUpScreen.tsx rename to src/accounts/SignUpScreen.tsx index e3f192d..3950bb0 100644 --- a/modules/accounts/SignUpScreen.tsx +++ b/src/accounts/SignUpScreen.tsx @@ -8,52 +8,127 @@ import { ActivityIndicator, Platform, } from 'react-native'; -import { observer } from 'mobx-react'; -import Colors from 'app/constants/Colors'; +import { useRecoilState, useSetRecoilState, useRecoilValue } from 'recoil'; +import Colors from 'app/src/constants/Colors'; import { useLinkTo } from '@react-navigation/native'; -import ValidationIcon from 'app/modules/accounts/ValidationIcon'; -import { Stores } from 'app/functions/Stores'; -import BackgroundImage from 'app/modules/ui/BackgroundImage'; -import WalletConnectBtn from 'app/modules/accounts/WalletConnectBtn'; +import ValidationIcon from 'app/src/accounts/ValidationIcon'; +import BackgroundImage from 'app/src/ui/BackgroundImage'; +import WalletConnectBtn from 'app/src/accounts/WalletConnectBtn'; import tw from 'twin.macro'; +import { SessionStore } from '../stores/session'; +import { + displayNameValidationErrorAtom, + emailValidationErrorAtom, + signUpBackendErrorAtom, + usernameValidationErrorAtom, + signUpValidAtom, + signUpErrorAtom, +} from '../atoms/atoms'; +import { WalletConnectStore } from '../stores/wallet-connect'; + +export default () => { + const { publicKey } = WalletConnectStore(); + const { signUp } = SessionStore(); + const [usernameValidation, setUsernameValidation] = useRecoilState( + usernameValidationErrorAtom + ); + const [displayNameValidation, setDisplayNameValidation] = useRecoilState( + displayNameValidationErrorAtom + ); + const [emailValidation, setEmailValidation] = useRecoilState( + emailValidationErrorAtom + ); + + const setBackendError = useSetRecoilState(signUpBackendErrorAtom); + const validForm = useRecoilValue(signUpValidAtom); + const error = useRecoilValue(signUpErrorAtom); -export default observer(() => { - const { signUpValidationStore, sessionStore, walletConnectStore } = Stores(); const [username, setUsername] = useState(''); const [displayName, setDisplayName] = useState(''); const [email, setEmail] = useState(''); const [loading, setLoading] = useState(false); const linkTo = useLinkTo(); + const validateUsername = (username: string) => { + if (!username) { + setUsernameValidation('Username is required.'); + return; + } + + if (username.length < 2) { + setUsernameValidation('Username is minimum 2 characters.'); + return; + } + + let validRegex = /^[a-zA-Z0-9_-]+$/.test(username); + if (!validRegex) { + setUsernameValidation( + 'Usernames cannot have spaces or special characters.' + ); + return; + } + + setUsernameValidation(''); + }; + const updateUsername = ({ target }: any) => { setUsername(target.value); - signUpValidationStore.validateUsername(target.value); + validateUsername(target.value); + }; + + const validateDisplayName = (displayName: string) => { + if (!displayName) { + setDisplayNameValidation('Display name is required.'); + return; + } + + if (displayName.length < 2) { + setDisplayNameValidation('Display name is minimum 2 characters.'); + return; + } + + setDisplayNameValidation(''); + }; + + const validateEmail = (email: string) => { + if (!email) { + setEmailValidation('Email is required.'); + return; + } + + var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + if (!re.test(email)) { + setEmailValidation('Please enter a valid email.'); + return; + } + + setEmailValidation(''); }; const updateDisplayName = ({ target }: any) => { setDisplayName(target.value); - signUpValidationStore.validateDisplayName(target.value); + validateDisplayName(target.value); }; const updateEmail = ({ target }: any) => { setEmail(target.value); - signUpValidationStore.validateEmail(target.value); + validateEmail(target.value); }; - const signUp = async () => { + const handleSignUp = async () => { setLoading(true); try { - await sessionStore.signUp({ + await signUp({ username: username, displayName: displayName, email: email, - publicKey: walletConnectStore.publicKey, + publicKey: publicKey, }); setLoading(false); return linkTo('/'); } catch (e) { if (typeof e === 'string') { - signUpValidationStore.setBackendError(e); + setBackendError(e); } } return setLoading(false); @@ -61,7 +136,7 @@ export default observer(() => { const onSubmit = (e) => { if (e.nativeEvent.key == 'Enter') { - signUp(); + handleSignUp(); } }; @@ -88,7 +163,7 @@ export default observer(() => { onChange={updateUsername} maxLength={30} /> - + { } maxLength={30} /> - + { maxLength={34} onKeyPress={onSubmit} /> - + - - {signUpValidationStore.error} + + {error} {loading ? ( { ); -}); +}; var styles = StyleSheet.create({ line: { diff --git a/modules/accounts/ValidationIcon.tsx b/src/accounts/ValidationIcon.tsx similarity index 67% rename from modules/accounts/ValidationIcon.tsx rename to src/accounts/ValidationIcon.tsx index 003673a..484d894 100644 --- a/modules/accounts/ValidationIcon.tsx +++ b/src/accounts/ValidationIcon.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import Colors from 'app/constants/Colors'; -import CheckIcon from 'app/modules/ui/icons/check'; -import CloseIcon from 'app/modules/ui/icons/x'; +import Colors from 'app/src/constants/Colors'; +import CheckIcon from 'app/src/ui/icons/check'; +import CloseIcon from 'app/src/ui/icons/x'; const validationIcon = (props) => { if (props.isFieldValid) { diff --git a/src/accounts/WalletConnectBtn.tsx b/src/accounts/WalletConnectBtn.tsx new file mode 100644 index 0000000..c819ec4 --- /dev/null +++ b/src/accounts/WalletConnectBtn.tsx @@ -0,0 +1,47 @@ +import React, { useEffect } from 'react'; +import { Pressable, Text } from 'react-native'; +import WalletConnectIcon from 'app/src/ui/icons/walletconnect-icon'; +import QRCodeModal from '@walletconnect/qrcode-modal'; +import tw from 'twin.macro'; +import { signManageDataOp } from 'app/src/stellar'; +import { WalletConnectStore } from '../stores/wallet-connect'; + +export default ({ signInWithXDR }: { signInWithXDR?: (_) => {} }) => { + let { uri, signXdr, publicKey, connect, state } = WalletConnectStore(); + + useEffect(() => { + if (!uri) return QRCodeModal.close(); + QRCodeModal.open(uri, () => {}, { + desktopLinks: [], + mobileLinks: ['lobstr'], + }); + }, [uri]); + + const handleSignInWithXdr = async (publicKey: string) => { + const xdr = await signManageDataOp(publicKey); + const { signedXDR } = await signXdr(xdr); + signInWithXDR && signInWithXDR(signedXDR); + }; + + useEffect(() => { + if (publicKey && signInWithXDR) handleSignInWithXdr(publicKey); + }, [publicKey]); + + return ( + connect()} + > + + {state === 'session-proposal' + ? 'Waiting for approval' + : state === 'session-created' + ? 'Connected' + : 'WalletConnect'} + + + + ); +}; diff --git a/skyhitz-common/src/algolia/algolia.ts b/src/algolia/algolia.ts similarity index 100% rename from skyhitz-common/src/algolia/algolia.ts rename to src/algolia/algolia.ts diff --git a/src/api/client.ts b/src/api/client.ts new file mode 100644 index 0000000..907b6ca --- /dev/null +++ b/src/api/client.ts @@ -0,0 +1,4 @@ +import { Config } from '../config'; +import { GraphQLClient } from 'graphql-request'; + +export const client = new GraphQLClient(Config.GRAPHQL_URL); diff --git a/skyhitz-common/src/backends/entries.backend.ts b/src/api/entries.ts similarity index 73% rename from skyhitz-common/src/backends/entries.backend.ts rename to src/api/entries.ts index 2eba567..fb6ff12 100644 --- a/skyhitz-common/src/backends/entries.backend.ts +++ b/src/api/entries.ts @@ -1,5 +1,5 @@ -import { client } from './apollo-client.backend'; -import { gql } from '@apollo/client'; +import { client } from './client'; +import { gql } from 'graphql-request'; import { Entry } from '../models/entry.model'; import { entriesIndex } from '../algolia/algolia'; @@ -17,17 +17,16 @@ export class EntriesBackend { async getPriceInfo(id: string) { return client - .query({ - query: gql` - { - entryPrice(id: "${id}"){ - price - amount - } - } - `, - }) - .then((data: any) => data.data) + .request( + gql` + { + entryPrice(id: "${id}"){ + price + amount + } + } + ` + ) .then(({ entryPrice }: any) => { return { price: @@ -45,8 +44,8 @@ export class EntriesBackend { async getById(id: string) { return client - .query({ - query: gql` + .request( + gql` { entries(id: "${id}"){ imageUrl @@ -59,9 +58,8 @@ export class EntriesBackend { issuer } } - `, - }) - .then((data: any) => data.data) + ` + ) .then(({ entries }: any) => { if (!entries.length) { return null; @@ -76,8 +74,8 @@ export class EntriesBackend { async getByUserId(userId: string) { return client - .query({ - query: gql` + .request( + gql` { entries(userId: "${userId}"){ imageUrl @@ -90,10 +88,8 @@ export class EntriesBackend { issuer } } - `, - fetchPolicy: 'network-only', - }) - .then((data: any) => data.data) + ` + ) .then(({ entries }: any) => { if (!entries.length) { return []; @@ -110,31 +106,30 @@ export class EntriesBackend { async buyEntry(id: string, amount: number, price: number) { return client - .mutate({ - mutation: gql` - mutation { - buyEntry(id: "${id}", amount: ${amount}, price: ${price}) { - xdr - success - submitted - } - } - `, - }) - .then(({ data }) => data) + .request( + gql` + mutation { + buyEntry(id: "${id}", amount: ${amount}, price: ${price}) { + xdr + success + submitted + } + } + ` + ) .then(({ buyEntry }: { buyEntry: ConditionalXdr }) => buyEntry); } async indexEntry(issuer: string) { return client - .mutate({ - mutation: gql` + .request( + gql` mutation { indexEntry(issuer: "${issuer}") } - `, - }) - .then(({ data }) => data.indexEntry); + ` + ) + .then(({ indexEntry }) => indexEntry); } async createFromUpload( @@ -146,8 +141,8 @@ export class EntriesBackend { equityForSale: number = 1 ) { return client - .mutate({ - mutation: gql` + .request( + gql` mutation { createEntry(fileCid: "${fileCid}",metaCid: "${metaCid}", code: "${code}", forSale: ${forSale}, price: ${price}, equityForSale: ${ equityForSale / 100 @@ -157,9 +152,8 @@ export class EntriesBackend { submitted } } - `, - }) - .then(({ data }) => data) + ` + ) .then(({ createEntry }: { createEntry: ConditionalXdr }) => createEntry) .catch((e) => { console.info(e); @@ -169,8 +163,8 @@ export class EntriesBackend { async getTopChart(page = 0): Promise { return client - .query({ - query: gql` + .request( + gql` { topChart(page: ${page} ) { imageUrl @@ -183,9 +177,8 @@ export class EntriesBackend { issuer } } - `, - }) - .then((data: any) => data.data) + ` + ) .then(({ topChart }: any) => { if (!topChart) { return []; @@ -204,8 +197,8 @@ export class EntriesBackend { async getRecentlyAdded(page = 0): Promise { return client - .query({ - query: gql` + .request( + gql` { recentlyAdded(page: ${page} ) { imageUrl @@ -218,9 +211,8 @@ export class EntriesBackend { issuer } } - `, - }) - .then((data: any) => data.data) + ` + ) .then(({ recentlyAdded }: any) => { if (!recentlyAdded) { return []; @@ -238,27 +230,24 @@ export class EntriesBackend { } remove(id: string) { - return client - .mutate({ - mutation: gql` + return client.request( + gql` mutation { removeEntry(id: "${id}") } - `, - }) - .then((data: any) => data.data); + ` + ); } getIssuer(cid: string) { return client - .query({ - query: gql` + .request( + gql` { getIssuer(cid: "${cid}") } - `, - }) - .then((data: any) => data.data) + ` + ) .then(({ getIssuer }) => getIssuer); } @@ -268,15 +257,13 @@ export class EntriesBackend { forSale: boolean, equityForSale: number ) { - return client - .mutate({ - mutation: gql` - mutation { - updatePricing(id: "${id}", price: ${price}, forSale: ${forSale}, equityForSale: ${equityForSale}) - } - `, - }) - .then((data: any) => data.data); + return client.request( + gql` + mutation { + updatePricing(id: "${id}", price: ${price}, forSale: ${forSale}, equityForSale: ${equityForSale}) + } + ` + ); } } diff --git a/skyhitz-common/src/backends/likes.backend.ts b/src/api/likes.ts similarity index 72% rename from skyhitz-common/src/backends/likes.backend.ts rename to src/api/likes.ts index 4d42836..8d31930 100644 --- a/skyhitz-common/src/backends/likes.backend.ts +++ b/src/api/likes.ts @@ -1,11 +1,11 @@ -import { client } from './apollo-client.backend'; -import { gql } from '@apollo/client'; +import { client } from './client'; +import { gql } from 'graphql-request'; export class LikesBackend { async userLikes() { return client - .query({ - query: gql` + .request( + gql` { userLikes { imageUrl @@ -16,16 +16,15 @@ export class LikesBackend { videoUrl } } - `, - }) - .then((data: any) => data.data) + ` + ) .then(({ userLikes }: any) => userLikes) .catch((e) => console.error(e)); } async entryLikes(id: string) { return client - .query({ - query: gql` + .request( + gql` { entryLikes(id: "${id}") { count @@ -37,22 +36,20 @@ export class LikesBackend { } } } - `, - }) - .then((data: any) => data.data) + ` + ) .then(({ entryLikes }: any) => entryLikes) .catch((e) => console.error(e)); } async like(id: string, like = true) { return client - .mutate({ - mutation: gql` + .request( + gql` mutation { likeEntry(id: "${id}", like: ${like}) } - `, - }) - .then((data: any) => data.data) + ` + ) .then(({ likeEntry }: any) => likeEntry) .catch((e) => console.error(e)); } diff --git a/src/api/payments.ts b/src/api/payments.ts new file mode 100644 index 0000000..f191c3c --- /dev/null +++ b/src/api/payments.ts @@ -0,0 +1,65 @@ +import { client } from './client'; +import { gql } from 'graphql-request'; + +export class PaymentsBackend { + async subscribe(cardToken: string) { + client + .request( + gql` + mutation { + subscribeUser(cardToken: "${cardToken}") + } + ` + ) + .then(({ subscribeUser }) => subscribeUser); + } + async buyCredits(cardToken: string, amount: number) { + return client + .request( + gql` + mutation { + buyCredits(cardToken: "${cardToken}", amount: ${amount}) + } + ` + ) + .then(({ buyCredits }) => buyCredits); + } + async getXLMPrice() { + return client + .request( + gql` + { + xlmPrice + } + ` + ) + .then(({ xlmPrice }) => xlmPrice); + } + async withdrawToExternalWallet(address: string, amount: number) { + return client + .request( + gql` + mutation { + withdrawToExternalWallet(address: "${address}", amount: ${amount}) + } + ` + ) + .then(({ withdrawToExternalWallet }) => withdrawToExternalWallet); + } + async refreshSubscription() { + return client + .request( + gql` + { + paymentsInfo { + subscribed + credits + } + } + ` + ) + .then(({ paymentsInfo }) => paymentsInfo); + } +} + +export const paymentsBackend = new PaymentsBackend(); diff --git a/src/api/user.ts b/src/api/user.ts new file mode 100644 index 0000000..c40089b --- /dev/null +++ b/src/api/user.ts @@ -0,0 +1,126 @@ +import { gql } from 'graphql-request'; +import { client } from './client'; +import { SignUpForm } from '../types'; + +export async function getAuthenticatedUser() { + return client + .request( + gql` + { + authenticatedUser { + avatarUrl + displayName + username + id + jwt + publishedAt + email + description + } + } + ` + ) + .then(({ authenticatedUser }) => authenticatedUser); +} + +export async function signUp({ + displayName, + email, + username, + publicKey, +}: SignUpForm) { + return client + .request( + gql` + mutation { + createUserWithEmail(displayName: "${displayName}", email: "${email}", username: "${username}",publicKey: "${publicKey}"){ + avatarUrl + displayName + username + id + jwt + publishedAt + email + description + publicKey + } + } + ` + ) + .then(({ createUserWithEmail }) => createUserWithEmail) + .catch(({ graphQLErrors }) => { + let [{ message }] = graphQLErrors; + throw message; + }); +} + +export async function requestToken(usernameOrEmail, publicKey) { + return client + .request( + gql` + mutation { + requestToken(usernameOrEmail: "${usernameOrEmail}", publicKey: "${publicKey}") + } + ` + ) + .catch((graphQLErrors) => { + let [{ message }] = graphQLErrors; + throw message; + }); +} + +export async function signIn(token?: string, uid?: string, xdr?: string) { + return client + .request( + gql` + mutation { + signIn(token: "${token}", uid: "${uid}", signedXDR: "${xdr}"){ + avatarUrl + displayName + username + id + jwt + publishedAt + email + description + publicKey + } + } + ` + ) + .then(({ signIn }) => signIn) + .catch(({ graphQLErrors }) => { + let [{ message }] = graphQLErrors; + throw message; + }); +} + +export async function updateUser( + avatarUrl: string, + displayName: string, + description: string, + username: string, + email: string +) { + client + .request( + gql` + mutation { + updateUser(avatarUrl: "${avatarUrl}", displayName: "${displayName}", description: "${description}", username: "${username}", email: "${email}"){ + avatarUrl + displayName + username + id + publishedAt + email + description + } + } + ` + ) + .then(({ updateUser }) => updateUser) + .catch(({ graphQLErrors }) => { + let [{ message }] = graphQLErrors; + throw message; + }); +} diff --git a/skyhitz-common/src/backends/users.backend.ts b/src/api/users.ts similarity index 100% rename from skyhitz-common/src/backends/users.backend.ts rename to src/api/users.ts diff --git a/src/atoms/atoms.ts b/src/atoms/atoms.ts new file mode 100644 index 0000000..0a94f5a --- /dev/null +++ b/src/atoms/atoms.ts @@ -0,0 +1,224 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { atom, selector, DefaultValue } from 'recoil'; +import { client } from '../api/client'; +import { userDataKey } from '../config/constants'; +import { User } from '../models'; +import { ProfileEdit } from '../models/profile'; + +const localStorageEffect = (key) => ({ setSelf, onSet }) => { + console.log('localstorage effect'); + setSelf( + AsyncStorage.getItem(key).then((savedValue) => + savedValue != null ? JSON.parse(savedValue) : new DefaultValue() + ) + ); + + onSet((newValue, _, isReset) => { + isReset + ? AsyncStorage.removeItem(key) + : AsyncStorage.setItem(key, JSON.stringify(newValue)); + }); +}; + +const headersEffect = () => ({ setSelf, onSet }) => { + onSet((newValue, _, isReset) => { + isReset + ? client.setHeader('authorization', '') + : client.setHeader('authorization', `Bearer ${newValue.jwt}`); + }); +}; + +export const userAtom = atom({ + key: 'user', + default: null, + effects_UNSTABLE: [localStorageEffect(userDataKey), headersEffect()], +}); + +export const profileAtom = atom({ + key: 'profileEdit', + default: selector({ + key: 'userSelect', + get: ({ get }) => { + const user = get(userAtom); + if (!user) + return { + avatarUrl: '', + displayName: '', + description: '', + username: '', + email: '', + loadingAvatar: false, + uploadError: '', + }; + const { + avatarUrl, + displayName, + description, + username, + email, + } = (user as unknown) as ProfileEdit; + return { + avatarUrl, + displayName, + description, + username, + email, + loadingAvatar: false, + uploadError: '', + }; + }, + }), +}); + +export const profileValidationErrorAtom = selector({ + key: 'profileValidationError', + get: ({ get }) => { + const { avatarUrl, displayName, description, username, email } = get( + profileAtom + ); + if (!avatarUrl) { + return 'Upload a profile picture.'; + } + + if (!displayName) { + return 'Add a display name.'; + } + + if (!description) { + return 'Add a description.'; + } + + if (!username) { + return 'Add a username.'; + } + + if (!email) { + return 'Add an email.'; + } + return null; + }, +}); + +export const canUpdateProfileAtom = selector({ + key: 'canUpdateProfile', + get: ({ get }) => { + const { avatarUrl, displayName, description, username, email } = get( + profileAtom + ); + return ( + !!avatarUrl && !!displayName && !!description && !!username && !!email + ); + }, +}); + +export const usernameValidationErrorAtom = atom({ + key: 'usernameValidationError', + default: '', +}); + +export const displayNameValidationErrorAtom = atom({ + key: 'displayNameValidationError', + default: '', +}); + +export const emailValidationErrorAtom = atom({ + key: 'emailValidationError', + default: '', +}); + +export const usernameOrEmailValidationErrorAtom = atom({ + key: 'usernameOrEmailValidationError', + default: '', +}); + +export const usernameOrEmailBackendErrorAtom = atom({ + key: 'usernameOrEmailBackendError', + default: '', +}); + +export const signUpBackendErrorAtom = atom({ + key: 'signUpBackendError', + default: '', +}); + +export const usernameOrEmailErrorAtom = selector({ + key: 'usernameOrEmailError', + get: ({ get }) => { + const usernameOrEmailBackendError = get(usernameOrEmailBackendErrorAtom); + const usernameOrEmailValidationError = get( + usernameOrEmailValidationErrorAtom + ); + + if (usernameOrEmailValidationError) { + return usernameOrEmailValidationError; + } + + if (usernameOrEmailBackendError) { + return usernameOrEmailBackendError; + } + + return ''; + }, +}); + +export const usernameOrEmailValidAtom = selector({ + key: 'usernameOrEmailValid', + get: ({ get }) => { + const usernameOrEmailError = get(usernameOrEmailValidationErrorAtom); + + if (usernameOrEmailError) { + return false; + } + + return true; + }, +}); + +export const signUpValidAtom = selector({ + key: 'signUpValid', + get: ({ get }) => { + const usernameValid = get(usernameValidationErrorAtom); + const displayNameValid = get(displayNameValidationErrorAtom); + const emailValid = get(emailValidationErrorAtom); + + if (usernameValid) { + return false; + } + if (displayNameValid) { + return false; + } + if (emailValid) { + return false; + } + + return true; + }, +}); + +export const signUpErrorAtom = selector({ + key: 'signUpError', + get: ({ get }) => { + const usernameValidationError = get(usernameValidationErrorAtom); + const displayNameValidationError = get(displayNameValidationErrorAtom); + const emailValidationError = get(emailValidationErrorAtom); + const signUpBackendError = get(signUpBackendErrorAtom); + + if (usernameValidationError) { + return usernameValidationError; + } + + if (displayNameValidationError) { + return displayNameValidationError; + } + + if (emailValidationError) { + return emailValidationError; + } + + if (signUpBackendError) { + return signUpBackendError; + } + + return ''; + }, +}); diff --git a/skyhitz-common/src/config/config.development.ts b/src/config/config.development.ts similarity index 100% rename from skyhitz-common/src/config/config.development.ts rename to src/config/config.development.ts diff --git a/skyhitz-common/src/config/config.production.ts b/src/config/config.production.ts similarity index 100% rename from skyhitz-common/src/config/config.production.ts rename to src/config/config.production.ts diff --git a/skyhitz-common/src/config/config.staging.ts b/src/config/config.staging.ts similarity index 100% rename from skyhitz-common/src/config/config.staging.ts rename to src/config/config.staging.ts diff --git a/skyhitz-common/src/config/config.ts b/src/config/config.ts similarity index 100% rename from skyhitz-common/src/config/config.ts rename to src/config/config.ts diff --git a/skyhitz-common/src/constants/constants.ts b/src/config/constants.ts similarity index 100% rename from skyhitz-common/src/constants/constants.ts rename to src/config/constants.ts diff --git a/skyhitz-common/src/config/index.ts b/src/config/index.ts similarity index 100% rename from skyhitz-common/src/config/index.ts rename to src/config/index.ts diff --git a/constants/Colors.ts b/src/constants/Colors.ts similarity index 100% rename from constants/Colors.ts rename to src/constants/Colors.ts diff --git a/constants/CursorPointer.ts b/src/constants/CursorPointer.ts similarity index 100% rename from constants/CursorPointer.ts rename to src/constants/CursorPointer.ts diff --git a/constants/Layout.ts b/src/constants/Layout.ts similarity index 100% rename from constants/Layout.ts rename to src/constants/Layout.ts diff --git a/functions/CacheResourcesAsync.ts b/src/functions/CacheResourcesAsync.ts similarity index 100% rename from functions/CacheResourcesAsync.ts rename to src/functions/CacheResourcesAsync.ts diff --git a/functions/utils.ts b/src/functions/utils.ts similarity index 89% rename from functions/utils.ts rename to src/functions/utils.ts index 3b0ceb4..4fcbbff 100644 --- a/functions/utils.ts +++ b/src/functions/utils.ts @@ -1,4 +1,4 @@ -import { Config } from 'app/skyhitz-common/src/config'; +import { Config } from 'app/src/config'; const stellarExplorer = 'https://stellar.expert/explorer/'; const horizonTestnet = 'https://horizon-testnet.stellar.org'; diff --git a/hooks/nft-listener.ts b/src/hooks/nft-listener.ts similarity index 51% rename from hooks/nft-listener.ts rename to src/hooks/nft-listener.ts index c132872..309c015 100644 --- a/hooks/nft-listener.ts +++ b/src/hooks/nft-listener.ts @@ -1,25 +1,29 @@ import { useEffect, useState } from 'react'; -import { Stores } from 'app/functions/Stores'; -import { Config } from 'app/skyhitz-common/src/config'; +import { Config } from 'app/src/config'; +import { userAtom } from '../atoms/atoms'; +import { useRecoilValue } from 'recoil'; +import { EntryStore } from '../stores/entry'; const EventSource = require('eventsource'); const nftListener = (open) => { - const { entryStore, sessionStore } = Stores(); + const { indexEntry, issuer } = EntryStore(); + const user = useRecoilValue(userAtom); + const [indexed, setIndexed] = useState(false); useEffect(() => { if (!open) return; const es = new EventSource( open - ? `${Config.HORIZON_URL}/accounts/${sessionStore.user?.publicKey}/payments?cursor=now&include_failed=false` + ? `${Config.HORIZON_URL}/accounts/${user?.publicKey}/payments?cursor=now&include_failed=false` : '' ); es.onmessage = async function (message) { const data = message.data ? JSON.parse(message.data) : message; - if ((data.type = 'payment' && entryStore.issuer === data.asset_issuer)) { - const indexEntry = await entryStore.indexEntry(data.asset_issuer); - if (indexEntry) { + if ((data.type = 'payment' && issuer === data.asset_issuer)) { + const res = await indexEntry(data.asset_issuer); + if (res) { setIndexed(true); } } @@ -31,9 +35,9 @@ const nftListener = (open) => { return () => { es.close(); }; - }, [sessionStore.user?.publicKey, open]); + }, [user?.publicKey, open]); - return { publicKey: sessionStore.user?.publicKey, indexed }; + return { publicKey: user?.publicKey, indexed }; }; export default nftListener; diff --git a/modules/marketing/ShareAppBanner.tsx b/src/marketing/ShareAppBanner.tsx similarity index 86% rename from modules/marketing/ShareAppBanner.tsx rename to src/marketing/ShareAppBanner.tsx index 97dcfff..4b01add 100644 --- a/modules/marketing/ShareAppBanner.tsx +++ b/src/marketing/ShareAppBanner.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { StyleSheet, View, Platform } from 'react-native'; -import Colors from 'app/constants/Colors'; -import LargeBtn from 'app/modules/ui/LargeBtn'; +import Colors from 'app/src/constants/Colors'; +import LargeBtn from 'app/src/ui/LargeBtn'; import { useLinkTo } from '@react-navigation/native'; -import UploadIcon from 'app/modules/ui/icons/upload'; -import DollarIcon from 'app/modules/ui/icons/dollar'; +import UploadIcon from 'app/src/ui/icons/upload'; +import DollarIcon from 'app/src/ui/icons/dollar'; export default ({ credits }) => { const linkTo = useLinkTo(); diff --git a/modules/marketing/web/Footer.tsx b/src/marketing/web/Footer.tsx similarity index 95% rename from modules/marketing/web/Footer.tsx rename to src/marketing/web/Footer.tsx index ec59550..fcc16c9 100644 --- a/modules/marketing/web/Footer.tsx +++ b/src/marketing/web/Footer.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Footer } from '@expo/html-elements'; import SocialLinks from './SocialLinks'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; import { useLinkTo } from '@react-navigation/native'; import { Text } from 'react-native'; diff --git a/modules/marketing/web/Home.tsx b/src/marketing/web/Home.tsx similarity index 81% rename from modules/marketing/web/Home.tsx rename to src/marketing/web/Home.tsx index 4ffd978..ecd17f7 100644 --- a/modules/marketing/web/Home.tsx +++ b/src/marketing/web/Home.tsx @@ -1,6 +1,6 @@ import React from 'react'; import NavBar from './NavBar'; -import BackgroundImage from 'app/modules/ui/BackgroundImage'; +import BackgroundImage from 'app/src/ui/BackgroundImage'; import MainWrapper from './MainWrapper'; import LandingFooter from './Footer'; diff --git a/modules/marketing/web/MainWrapper.tsx b/src/marketing/web/MainWrapper.tsx similarity index 96% rename from modules/marketing/web/MainWrapper.tsx rename to src/marketing/web/MainWrapper.tsx index 707d7a4..c895bd4 100644 --- a/modules/marketing/web/MainWrapper.tsx +++ b/src/marketing/web/MainWrapper.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { H1, Main, P } from '@expo/html-elements'; import { View, Text, StyleSheet, Pressable } from 'react-native'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; import { useMediaQuery } from 'react-responsive'; import { useLinkTo } from '@react-navigation/native'; -import cursorPointer from 'app/constants/CursorPointer'; +import cursorPointer from 'app/src/constants/CursorPointer'; // https://apps.apple.com/us/app/skyhitz/id1105406020 diff --git a/modules/marketing/web/NavBar.tsx b/src/marketing/web/NavBar.tsx similarity index 90% rename from modules/marketing/web/NavBar.tsx rename to src/marketing/web/NavBar.tsx index 4349ea7..a95cfd3 100644 --- a/modules/marketing/web/NavBar.tsx +++ b/src/marketing/web/NavBar.tsx @@ -3,12 +3,12 @@ import { Nav } from '@expo/html-elements'; import SkyhitzLogo from './SkyhitzLogo'; import { View, Text, StyleSheet, Pressable } from 'react-native'; import { useLinkTo } from '@react-navigation/native'; -import cursorPointer from 'app/constants/CursorPointer'; -import { observer } from 'mobx-react'; -import { Stores } from 'app/functions/Stores'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import { userAtom } from 'app/src/atoms/atoms'; +import { useRecoilValue } from 'recoil'; -export default observer(() => { - const { sessionStore } = Stores(); +export default () => { + const user = useRecoilValue(userAtom); const linkTo = useLinkTo(); @@ -45,7 +45,7 @@ export default observer(() => { paddingRight: 20, }} > - {sessionStore.user ? null : ( + {user ? null : ( <> linkTo('/accounts/sign-in')} @@ -64,7 +64,7 @@ export default observer(() => { ); -}); +}; let styles = StyleSheet.create({ logo: { diff --git a/modules/marketing/web/Privacy.tsx b/src/marketing/web/Privacy.tsx similarity index 99% rename from modules/marketing/web/Privacy.tsx rename to src/marketing/web/Privacy.tsx index 9f32711..dfc06d5 100644 --- a/modules/marketing/web/Privacy.tsx +++ b/src/marketing/web/Privacy.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; -import NavBar from 'app/modules/marketing/web/NavBar'; +import NavBar from 'app/src/marketing/web/NavBar'; import { H1, H2, BR, P } from '@expo/html-elements'; export default () => { diff --git a/modules/marketing/web/SkyhitzLogo.tsx b/src/marketing/web/SkyhitzLogo.tsx similarity index 100% rename from modules/marketing/web/SkyhitzLogo.tsx rename to src/marketing/web/SkyhitzLogo.tsx diff --git a/modules/marketing/web/SocialLinks.tsx b/src/marketing/web/SocialLinks.tsx similarity index 85% rename from modules/marketing/web/SocialLinks.tsx rename to src/marketing/web/SocialLinks.tsx index 9139375..5cf5049 100644 --- a/modules/marketing/web/SocialLinks.tsx +++ b/src/marketing/web/SocialLinks.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; import { A } from '@expo/html-elements'; import { View } from 'react-native'; let Anchor: React.ComponentType = A; -import Github from 'app/modules/ui/icons/github'; -import Insta from 'app/modules/ui/icons/instagram'; -import Discord from 'app/modules/ui/icons/discord'; -import Twitter from 'app/modules/ui/icons/twitter'; +import Github from 'app/src/ui/icons/github'; +import Insta from 'app/src/ui/icons/instagram'; +import Discord from 'app/src/ui/icons/discord'; +import Twitter from 'app/src/ui/icons/twitter'; export default () => { return ( diff --git a/modules/marketing/web/Terms.tsx b/src/marketing/web/Terms.tsx similarity index 99% rename from modules/marketing/web/Terms.tsx rename to src/marketing/web/Terms.tsx index 37dfa48..31a21b0 100644 --- a/modules/marketing/web/Terms.tsx +++ b/src/marketing/web/Terms.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; -import NavBar from 'app/modules/marketing/web/NavBar'; +import NavBar from 'app/src/marketing/web/NavBar'; import { H1, H2, BR, P, Section } from '@expo/html-elements'; export default () => { diff --git a/skyhitz-common/src/models/entry.model.ts b/src/models/entry.model.ts similarity index 96% rename from skyhitz-common/src/models/entry.model.ts rename to src/models/entry.model.ts index a1e1f5b..6c185ad 100644 --- a/skyhitz-common/src/models/entry.model.ts +++ b/src/models/entry.model.ts @@ -3,7 +3,7 @@ import { videosGateway, imagesGateway, skyhitzCdn, -} from '../constants/constants'; +} from '../config/constants'; import { Payload } from './payload.model'; export class EntryPayload extends Payload { @@ -22,6 +22,7 @@ export class EntryPayload extends Payload { price?: number; issuer?: string; code?: string; + artist?: string; } export class Entry extends EntryPayload { @@ -42,6 +43,7 @@ export class Entry extends EntryPayload { this.price = payload.price; this.issuer = payload.issuer; this.code = payload.code; + this.artist = payload.artist; } get isIpfs() { diff --git a/skyhitz-common/src/models/index.ts b/src/models/index.ts similarity index 100% rename from skyhitz-common/src/models/index.ts rename to src/models/index.ts diff --git a/skyhitz-common/src/models/payload.model.ts b/src/models/payload.model.ts similarity index 100% rename from skyhitz-common/src/models/payload.model.ts rename to src/models/payload.model.ts diff --git a/src/models/profile.ts b/src/models/profile.ts new file mode 100644 index 0000000..7b2bccd --- /dev/null +++ b/src/models/profile.ts @@ -0,0 +1,9 @@ +export type ProfileEdit = { + avatarUrl: string; + displayName: string; + description: string; + username: string; + email: string; + loadingAvatar: boolean; + uploadError: string; +}; diff --git a/skyhitz-common/src/models/user.model.ts b/src/models/user.model.ts similarity index 79% rename from skyhitz-common/src/models/user.model.ts rename to src/models/user.model.ts index 5d0aa9f..ed489be 100644 --- a/skyhitz-common/src/models/user.model.ts +++ b/src/models/user.model.ts @@ -1,6 +1,20 @@ import { Payload } from './payload.model'; -class UserPayload extends Payload { +export type UserData = { + avatarUrl?: string; + displayName?: string; + email?: string; + reputation?: number; + publishedAt?: string; + username?: string; + id?: string; + userType?: number; + jwt?: string; + description?: string; + publicKey?: string; +}; + +class UserPayload extends Payload implements UserData { avatarUrl?: string; displayName?: string; email?: string; diff --git a/modules/navigation/LazyAppStackNavigator.tsx b/src/navigation/AppStackNavigator.tsx similarity index 55% rename from modules/navigation/LazyAppStackNavigator.tsx rename to src/navigation/AppStackNavigator.tsx index 4024583..ecb1ce6 100644 --- a/modules/navigation/LazyAppStackNavigator.tsx +++ b/src/navigation/AppStackNavigator.tsx @@ -1,114 +1,26 @@ -import React, { lazy } from 'react'; +import React from 'react'; import { createStackNavigator } from '@react-navigation/stack'; -import { SuspenseLoading } from './SuspenseLoading'; -import Colors from 'app/constants/Colors'; +import { useMediaQuery } from 'react-responsive'; +import Colors from 'app/src/constants/Colors'; import { Platform } from 'react-native'; import CancelEditBtn from '../ui/CancelEditBtn'; import DoneEditBtn from '../ui/DoneEditBtn'; - -const MainTabNavigator = lazy(() => - import('app/modules/navigation/MainTabNavigator') -); - -const MainTabNavigatorSuspense = (props) => ( - - - -); - -const EditProfileScreen = lazy(() => - import('app/modules/profile/EditProfileScreen') -); - -const EditProfileScreenSuspense = (props) => ( - - - -); -const EntryOptionsModal = lazy(() => - import('app/modules/search/EntryOptionsModal') -); - -const EntryOptionsModalSuspense = (props) => ( - - - -); -const PricingOptionsModal = lazy(() => - import('app/modules/search/PricingOptionsModal') -); -const PricingOptionsModalSuspense = (props) => ( - - - -); - -const PaymentModal = lazy(() => import('app/modules/profile/PaymentModal')); -const PaymentModalSuspense = (props) => ( - - - -); - -const LowBalanceModal = lazy(() => - import('app/modules/profile/LowBalanceModal') -); -const LowBalanceModalSuspense = (props) => ( - - - -); - -const WithdrawalModal = lazy(() => - import('app/modules/profile/WithdrawalModal') -); -const WithdrawalModalSuspense = (props) => ( - - - -); -const BuyOptionsModal = lazy(() => import('app/modules/ui/BuyOptionsModal')); -const BuyOptionsModalSuspense = (props) => ( - - - -); -const AuthScreen = lazy(() => import('app/modules/accounts/AuthScreen')); -const AuthScreenSuspense = (props) => ( - - - -); -const SignUpScreen = lazy(() => import('app/modules/accounts/SignUpScreen')); -const SignUpScreenSuspense = (props) => ( - - - -); -const SignInScreen = lazy(() => import('app/modules/accounts/SignInScreen')); -const SignInScreenSuspense = (props) => ( - - - -); -const WebApp = lazy(() => import('app/modules/marketing/web/Home')); -const WebAppSuspense = (props) => ( - - - -); -const Privacy = lazy(() => import('app/modules/marketing/web/Privacy')); -const PrivacySuspense = (props) => ( - - - -); -const Terms = lazy(() => import('app/modules/marketing/web/Terms')); -const TermsSuspense = (props) => ( - - - -); +import { useRecoilValue } from 'recoil'; +import { userAtom } from '../atoms/atoms'; +import SignInScreen from 'app/src/accounts/SignInScreen'; +import MainTabNavigator from 'app/src/navigation/MainTabNavigator'; +import EditProfileModal from 'app/src/profile/EditProfileScreen'; +import EntryOptionsModal from 'app/src/search/EntryOptionsModal'; +import PricingOptionsModal from 'app/src/search/PricingOptionsModal'; +import PaymentModal from 'app/src/profile/PaymentModal'; +import LowBalanceModal from 'app/src/profile/LowBalanceModal'; +import WithdrawalModal from 'app/src/profile/WithdrawalModal'; +import BuyOptionsModal from 'app/src/ui/BuyOptionsModal'; +import AuthScreen from 'app/src/accounts/AuthScreen'; +import SignUpScreen from 'app/src/accounts/SignUpScreen'; +import WebApp from 'app/src/marketing/web/Home'; +import Privacy from 'app/src/marketing/web/Privacy'; +import Terms from 'app/src/marketing/web/Terms'; const modalOptions = { headerShown: false, @@ -136,7 +48,10 @@ export const AppStack = createStackNavigator(); const appTitle = 'Skyhitz - Music NFTs on Stellar'; -export function LazyAppStackNavigator({ user, headerShown }) { +export function AppStackNavigator() { + const user = useRecoilValue(userAtom); + const isDesktop = useMediaQuery({ minWidth: 768 }); + const headerShown = !isDesktop; return ( MainTabNavigatorSuspense} + getComponent={() => MainTabNavigator} options={{ headerShown: false }} /> @@ -158,7 +73,7 @@ export function LazyAppStackNavigator({ user, headerShown }) { {Platform.OS === 'web' ? ( WebAppSuspense} + getComponent={() => WebApp} options={{ headerShown: false, title: appTitle, @@ -167,7 +82,7 @@ export function LazyAppStackNavigator({ user, headerShown }) { ) : ( AuthScreenSuspense} + getComponent={() => AuthScreen} options={{ headerShown: false, gestureEnabled: false, @@ -177,7 +92,7 @@ export function LazyAppStackNavigator({ user, headerShown }) { )} SignUpScreenSuspense} + getComponent={() => SignUpScreen} options={{ headerShown: headerShown, headerTitleStyle: { color: Colors.white }, @@ -191,7 +106,7 @@ export function LazyAppStackNavigator({ user, headerShown }) { /> SignInScreenSuspense} + getComponent={() => SignInScreen} options={{ headerShown: headerShown, headerTitleStyle: { color: Colors.white }, @@ -205,7 +120,7 @@ export function LazyAppStackNavigator({ user, headerShown }) { /> PrivacySuspense} + getComponent={() => Privacy} options={{ headerShown: headerShown, headerTitleStyle: { color: Colors.white }, @@ -219,7 +134,7 @@ export function LazyAppStackNavigator({ user, headerShown }) { /> TermsSuspense} + getComponent={() => Terms} options={{ headerShown: headerShown, headerTitleStyle: { color: Colors.white }, @@ -237,7 +152,7 @@ export function LazyAppStackNavigator({ user, headerShown }) { EditProfileScreenSuspense} + getComponent={() => EditProfileModal} options={{ gestureEnabled: false, title: 'Edit Profile', @@ -257,32 +172,32 @@ export function LazyAppStackNavigator({ user, headerShown }) { PaymentModalSuspense} + getComponent={() => PaymentModal} options={modalOptions} /> WithdrawalModalSuspense} + getComponent={() => WithdrawalModal} options={modalOptions} /> BuyOptionsModalSuspense} + getComponent={() => BuyOptionsModal} options={modalOptions} /> EntryOptionsModalSuspense} + getComponent={() => EntryOptionsModal} options={modalOptions} /> PricingOptionsModalSuspense} + getComponent={() => PricingOptionsModal} options={modalOptions} /> LowBalanceModalSuspense} + getComponent={() => LowBalanceModal} options={modalOptions} /> diff --git a/modules/navigation/BottomTabBar.tsx b/src/navigation/BottomTabBar.tsx similarity index 100% rename from modules/navigation/BottomTabBar.tsx rename to src/navigation/BottomTabBar.tsx diff --git a/modules/navigation/BottomTabView.tsx b/src/navigation/BottomTabView.tsx similarity index 100% rename from modules/navigation/BottomTabView.tsx rename to src/navigation/BottomTabView.tsx diff --git a/modules/navigation/LinkingConfiguration.tsx b/src/navigation/LinkingConfiguration.tsx similarity index 100% rename from modules/navigation/LinkingConfiguration.tsx rename to src/navigation/LinkingConfiguration.tsx diff --git a/modules/navigation/MainTabNavigator.tsx b/src/navigation/MainTabNavigator.tsx similarity index 87% rename from modules/navigation/MainTabNavigator.tsx rename to src/navigation/MainTabNavigator.tsx index 04d17be..425146f 100644 --- a/modules/navigation/MainTabNavigator.tsx +++ b/src/navigation/MainTabNavigator.tsx @@ -1,9 +1,9 @@ import React, { lazy } from 'react'; import { View } from 'react-native'; -import ProfileSettingsScreen from 'app/modules/profile/ProfileSettingsScreen'; -import SearchNavigator from 'app/modules/search/SearchNavigator'; -import Colors from 'app/constants/Colors'; -import ChartsView from 'app/modules/search/ChartsView'; +import ProfileSettingsScreen from 'app/src/profile/ProfileSettingsScreen'; +import SearchNavigator from 'app/src/search/SearchNavigator'; +import Colors from 'app/src/constants/Colors'; +import ChartsView from 'app/src/search/ChartsView'; import BottomTabBar from './BottomTabBar'; import createBottomTabNavigator from './WebTabNavigator'; @@ -11,11 +11,11 @@ import { useMediaQuery } from 'react-responsive'; import MiniPlayerDesktop from '../player/player-bar/MiniPlayerDesktop'; import SkyhitzLogo from '../marketing/web/SkyhitzLogo'; import { SuspenseLoading } from './SuspenseLoading'; -import SearchIcon from 'app/modules/ui/icons/search'; -import UserIcon from 'app/modules/ui/icons/user'; +import SearchIcon from 'app/src/ui/icons/search'; +import UserIcon from 'app/src/ui/icons/user'; const PlayerDrawer = lazy(() => - import('app/modules/player/player-bar/PlayerDrawer') + import('app/src/player/player-bar/PlayerDrawer') ); const PlayerDrawerSuspense = (props) => ( diff --git a/modules/navigation/LazyNavigationContainer.tsx b/src/navigation/RootNavigation.tsx similarity index 56% rename from modules/navigation/LazyNavigationContainer.tsx rename to src/navigation/RootNavigation.tsx index bff5bf2..f03e14c 100644 --- a/modules/navigation/LazyNavigationContainer.tsx +++ b/src/navigation/RootNavigation.tsx @@ -1,8 +1,10 @@ import React from 'react'; +import { StatusBar } from 'react-native'; +import { AppStackNavigator } from 'app/src/navigation/AppStackNavigator'; import { NavigationContainer, DefaultTheme } from '@react-navigation/native'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; import LinkingConfiguration from './LinkingConfiguration'; -import LoadingScreen from 'app/modules/accounts/LoadingScreen'; +import LoadingScreen from 'app/src/accounts/LoadingScreen'; const Theme = { ...DefaultTheme, @@ -13,14 +15,18 @@ const Theme = { }, }; -export default function LazyNavigationContainer(props) { +const RootNavigation = () => { + StatusBar.setBarStyle('light-content'); + return ( } theme={Theme} > - {props.children} + ); -} +}; + +export default RootNavigation; diff --git a/modules/navigation/SuspenseLoading.tsx b/src/navigation/SuspenseLoading.tsx similarity index 100% rename from modules/navigation/SuspenseLoading.tsx rename to src/navigation/SuspenseLoading.tsx diff --git a/modules/navigation/WebTabNavigator.tsx b/src/navigation/WebTabNavigator.tsx similarity index 100% rename from modules/navigation/WebTabNavigator.tsx rename to src/navigation/WebTabNavigator.tsx diff --git a/modules/player/player-bar/MiniPlayer.tsx b/src/player/player-bar/MiniPlayer.tsx similarity index 75% rename from modules/player/player-bar/MiniPlayer.tsx rename to src/player/player-bar/MiniPlayer.tsx index 8a0d599..6d35471 100644 --- a/modules/player/player-bar/MiniPlayer.tsx +++ b/src/player/player-bar/MiniPlayer.tsx @@ -1,21 +1,17 @@ -import { observer } from 'mobx-react'; import React from 'react'; import { View, Text, StyleSheet, Pressable } from 'react-native'; -import { Stores } from 'app/functions/Stores'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; import PlayBtnSmall from './play-btn-small/PlayBtnSmall'; -import cursorPointer from 'app/constants/CursorPointer'; -import ChevronUpIcon from 'app/modules/ui/icons/chevron-up'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import ChevronUpIcon from 'app/src/ui/icons/chevron-up'; +import { PlayerStore } from 'app/src/stores/player'; -export default observer(() => { - let { playerStore } = Stores(); +export default () => { + const { setShow, entry } = PlayerStore(); return ( - playerStore.showPlayer()} - style={[cursorPointer]} - > + setShow(true)} style={[cursorPointer]}> @@ -25,16 +21,14 @@ export default observer(() => { ellipsizeMode="tail" numberOfLines={1} > - {playerStore.entry - ? playerStore.entry.title + ' - ' + playerStore.entry.artist - : ''} + {entry ? entry.title + ' - ' + entry.artist : ''} ); -}); +}; let styles = StyleSheet.create({ bg: { diff --git a/modules/player/player-bar/MiniPlayerDesktop.tsx b/src/player/player-bar/MiniPlayerDesktop.tsx similarity index 91% rename from modules/player/player-bar/MiniPlayerDesktop.tsx rename to src/player/player-bar/MiniPlayerDesktop.tsx index 9f5a7a2..decd6a2 100644 --- a/modules/player/player-bar/MiniPlayerDesktop.tsx +++ b/src/player/player-bar/MiniPlayerDesktop.tsx @@ -11,26 +11,25 @@ import { } from '../player-screen/video-player/VideoTimeDisplay'; import Slider from '../player-screen/slider/Slider'; import VideoComponent from '../player-screen/video-player/VideoComponent'; -import Colors from 'app/constants/Colors'; -import { Stores } from 'app/functions/Stores'; -import { observer } from 'mobx-react'; +import Colors from 'app/src/constants/Colors'; +import { PlayerStore } from 'app/src/stores/player'; -export default observer(() => { - let { playerStore } = Stores(); +export default () => { + let { entry } = PlayerStore(); return ( - {playerStore.entry?.title} + {entry?.title} - {playerStore.entry?.artist} + {entry?.artist} @@ -51,7 +50,7 @@ export default observer(() => { ); -}); +}; let styles = StyleSheet.create({ rowControls: { diff --git a/modules/player/player-bar/PlayerDrawer.tsx b/src/player/player-bar/PlayerDrawer.tsx similarity index 93% rename from modules/player/player-bar/PlayerDrawer.tsx rename to src/player/player-bar/PlayerDrawer.tsx index 0fea8e5..11b80ed 100644 --- a/modules/player/player-bar/PlayerDrawer.tsx +++ b/src/player/player-bar/PlayerDrawer.tsx @@ -1,14 +1,13 @@ import React, { useEffect } from 'react'; -import { observer } from 'mobx-react'; import { StyleSheet, Dimensions } from 'react-native'; import PlayerScreen from '../player-screen/PlayerScreen'; import MiniPlayer from './MiniPlayer'; import { State } from 'react-native-gesture-handler'; import { getBottomSpace } from 'react-native-iphone-x-helper'; import { clamp, timing, withSpring } from 'react-native-redash/lib/module/v1'; -import { Stores } from 'app/functions/Stores'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; import Animated from 'react-native-reanimated'; +import { PlayerStore } from 'app/src/stores/player'; let tabNavBottom = 89; @@ -48,8 +47,8 @@ const state = new Value(State.UNDETERMINED); const offset = new Value(SNAP_BOTTOM); const clock = new Clock(); -export default observer(({ children }) => { - const { playerStore } = Stores(); +export default ({ children }) => { + const { show, entry } = PlayerStore(); const translateY = withSpring({ value: clamp(translationY, SNAP_TOP, SNAP_BOTTOM), @@ -85,12 +84,12 @@ export default observer(({ children }) => { }); useEffect(() => { - if (playerStore.show) { + if (show) { goUp.setValue(1 as any); } else { goDown.setValue(1 as any); } - }, [playerStore.show]); + }, [show]); useCode( () => @@ -126,7 +125,7 @@ export default observer(({ children }) => { { ); -}); +}; let styles = StyleSheet.create({ playerSheet: { diff --git a/modules/player/player-bar/play-btn-small/PlayBtnSmall.tsx b/src/player/player-bar/play-btn-small/PlayBtnSmall.tsx similarity index 52% rename from modules/player/player-bar/play-btn-small/PlayBtnSmall.tsx rename to src/player/player-bar/play-btn-small/PlayBtnSmall.tsx index 990a385..f942dd7 100644 --- a/modules/player/player-bar/play-btn-small/PlayBtnSmall.tsx +++ b/src/player/player-bar/play-btn-small/PlayBtnSmall.tsx @@ -1,19 +1,18 @@ import React from 'react'; -import { observer } from 'mobx-react'; import { StyleSheet, Pressable } from 'react-native'; -import Play from 'app/modules/ui/icons/play'; -import Pause from 'app/modules/ui/icons/pause'; -import { Stores } from 'app/functions/Stores'; -import cursorPointer from 'app/constants/CursorPointer'; +import Play from 'app/src/ui/icons/play'; +import Pause from 'app/src/ui/icons/pause'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import { PlayerStore } from 'app/src/stores/player'; -export default observer(() => { - let { playerStore } = Stores(); +export default () => { + let { isPlaying, pauseAsync, playAsync } = PlayerStore(); - if (playerStore.isPlaying) { + if (isPlaying()) { return ( playerStore.pauseAsync()} + onPress={() => pauseAsync()} > @@ -22,12 +21,12 @@ export default observer(() => { return ( playerStore.playAsync()} + onPress={() => playAsync()} > ); -}); +}; let styles = StyleSheet.create({ playBtnWrapper: { diff --git a/modules/player/player-screen/PlayerEntryInfo.tsx b/src/player/player-screen/PlayerEntryInfo.tsx similarity index 81% rename from modules/player/player-screen/PlayerEntryInfo.tsx rename to src/player/player-screen/PlayerEntryInfo.tsx index a5dd66a..dbfc8b6 100644 --- a/modules/player/player-screen/PlayerEntryInfo.tsx +++ b/src/player/player-screen/PlayerEntryInfo.tsx @@ -1,12 +1,9 @@ import React from 'react'; import { StyleSheet, View, Text } from 'react-native'; -import { inject } from 'mobx-react'; -import * as stores from 'app/skyhitz-common'; -type Stores = typeof stores; +import { PlayerStore } from 'app/src/stores/player'; -const PlayerEntryInfo = inject((stores: Stores) => ({ - entry: stores.playerStore.entry, -}))(({ entry }: any) => { +const PlayerEntryInfo = () => { + const { entry } = PlayerStore(); if (!entry) { return null; } @@ -24,7 +21,7 @@ const PlayerEntryInfo = inject((stores: Stores) => ({ ); -}); +}; export default PlayerEntryInfo; diff --git a/modules/player/player-screen/PlayerNav.tsx b/src/player/player-screen/PlayerNav.tsx similarity index 72% rename from modules/player/player-screen/PlayerNav.tsx rename to src/player/player-screen/PlayerNav.tsx index 1e3980c..07f9cac 100644 --- a/modules/player/player-screen/PlayerNav.tsx +++ b/src/player/player-screen/PlayerNav.tsx @@ -1,23 +1,22 @@ import React from 'react'; import { StyleSheet, View, Text, Pressable } from 'react-native'; -import { observer } from 'mobx-react'; -import ChevronDown from 'app/modules/ui/icons/chevron-down'; -import Colors from 'app/constants/Colors'; -import { Stores } from 'app/functions/Stores'; +import ChevronDown from 'app/src/ui/icons/chevron-down'; +import Colors from 'app/src/constants/Colors'; +import { PlayerStore } from 'app/src/stores/player'; -export default observer(({ onPress }) => { - let { playerStore } = Stores(); +export default ({ onPress }) => { + let { entry } = PlayerStore(); return ( - {playerStore.entry?.artist} + {entry?.artist} ); -}); +}; let styles = StyleSheet.create({ playerNav: { diff --git a/modules/player/player-screen/PlayerScreen.tsx b/src/player/player-screen/PlayerScreen.tsx similarity index 76% rename from modules/player/player-screen/PlayerScreen.tsx rename to src/player/player-screen/PlayerScreen.tsx index 6c786ed..806ce75 100644 --- a/modules/player/player-screen/PlayerScreen.tsx +++ b/src/player/player-screen/PlayerScreen.tsx @@ -6,16 +6,15 @@ import { View, Pressable, } from 'react-native'; -import ChevronDown from 'app/modules/ui/icons/chevron-down'; +import ChevronDown from 'app/src/ui/icons/chevron-down'; import VideoPlayer from './video-player/VideoPlayer'; import PlayerEntryInfo from './PlayerEntryInfo'; -import BuyBtn from 'app/modules/ui/buy-btn/BuyBtn'; +import BuyBtn from 'app/src/ui/buy-btn/BuyBtn'; import PlayerControls from './player-controls/PlayerControls'; import LikersSection from './likers-section/LikersSection'; -import Colors from 'app/constants/Colors'; -import { observer } from 'mobx-react'; -import { Stores } from 'app/functions/Stores'; -import cursorPointer from 'app/constants/CursorPointer'; +import Colors from 'app/src/constants/Colors'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import { PlayerStore } from 'app/src/stores/player'; const { width } = Dimensions.get('window'); const styles = StyleSheet.create({ @@ -62,24 +61,24 @@ const styles = StyleSheet.create({ }, }); -export default observer(() => { - let { playerStore } = Stores(); +export default () => { + let { hidePlayer, entry } = PlayerStore(); return ( playerStore.hidePlayer()} + onPress={() => hidePlayer()} > - + ); -}); +}; diff --git a/src/player/player-screen/forward-btn/ForwardBtn.tsx b/src/player/player-screen/forward-btn/ForwardBtn.tsx new file mode 100644 index 0000000..e382b12 --- /dev/null +++ b/src/player/player-screen/forward-btn/ForwardBtn.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { StyleSheet, Pressable } from 'react-native'; +import SkipForwardIcon from 'app/src/ui/icons/skip-forward'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import Colors from 'app/src/constants/Colors'; +import { PlayerStore } from 'app/src/stores/player'; + +const ForwardBtn = ({ size = 24 }) => { + const { playNext } = PlayerStore(); + + return ( + playNext()} + > + + + ); +}; + +export default ForwardBtn; + +var styles = StyleSheet.create({ + controlTouch: { + alignSelf: 'center', + }, + forwardBtn: { + width: 24, + height: 18, + }, +}); diff --git a/src/player/player-screen/like-btn/LikeBtn.tsx b/src/player/player-screen/like-btn/LikeBtn.tsx new file mode 100644 index 0000000..f98d9ef --- /dev/null +++ b/src/player/player-screen/like-btn/LikeBtn.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { StyleSheet, Pressable } from 'react-native'; +import LikeIcon from 'app/src/ui/icons/like'; +import Colors from 'app/src/constants/Colors'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import { LikesStore } from 'app/src/stores/likes'; +import { PlayerStore } from 'app/src/stores/player'; + +export default (props) => { + const { toggleLike, isLiked } = LikesStore(); + const { entry } = PlayerStore(); + + if (!entry) { + return null; + } + if (isLiked()) { + return ( + toggleLike(entry)} + > + + + ); + } + return ( + toggleLike(entry)} + > + + + ); +}; + +var styles = StyleSheet.create({ + controlTouch: { + width: 32, + height: 28, + }, +}); diff --git a/src/player/player-screen/likers-section/LikersSection.tsx b/src/player/player-screen/likers-section/LikersSection.tsx new file mode 100644 index 0000000..5404a78 --- /dev/null +++ b/src/player/player-screen/likers-section/LikersSection.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import { StyleSheet, View, Text } from 'react-native'; +import LikeBtn from 'app/src/player/player-screen/like-btn/LikeBtn'; +import Divider from 'app/src/ui/Divider'; +import { UserAvatar } from 'app/src/ui/UserAvatar'; +import { LikesStore } from 'app/src/stores/likes'; + +export default () => { + const { entryLikes, hasMoreLikers, plusLikers } = LikesStore(); + + const renderMoreLikersBtn = () => { + return ( + hasMoreLikers() && ( + + +{plusLikers} + + ) + ); + }; + + return ( + + + Liked By + + + + + + + {entryLikes && + entryLikes.map((liker) => ( + + + + ))} + {renderMoreLikersBtn()} + + + ); +}; + +let styles = StyleSheet.create({ + bottomSection: { + width: '100%', + paddingLeft: 15, + paddingRight: 15, + flexDirection: 'column', + justifyContent: 'flex-end', + maxWidth: 650, + alignSelf: 'center', + }, + likedByWrap: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + actionsWrap: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + likedByText: { + height: 30, + lineHeight: 30, + fontSize: 14, + color: 'white', + textAlign: 'left', + width: 100, + }, + likers: { + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'flex-start', + flexWrap: 'nowrap', + marginTop: 10, + marginBottom: 15, + minHeight: 30, + }, + liker: { + marginRight: 7, + }, + profilepic: { + borderRadius: 15, + width: 30, + height: 30, + }, + plusFriendsCircle: { + borderRadius: 15, + width: 30, + height: 30, + backgroundColor: '#9a9999', + }, + plusFriends: { + color: 'white', + fontSize: 13, + textAlign: 'center', + backgroundColor: 'transparent', + flex: 1, + paddingTop: 8, + }, +}); diff --git a/modules/player/player-screen/loop-btn/LoopBtn.tsx b/src/player/player-screen/loop-btn/LoopBtn.tsx similarity index 53% rename from modules/player/player-screen/loop-btn/LoopBtn.tsx rename to src/player/player-screen/loop-btn/LoopBtn.tsx index dcf6e42..4e62704 100644 --- a/modules/player/player-screen/loop-btn/LoopBtn.tsx +++ b/src/player/player-screen/loop-btn/LoopBtn.tsx @@ -1,19 +1,18 @@ import React from 'react'; import { StyleSheet, Pressable } from 'react-native'; -import { observer } from 'mobx-react'; -import LoopIcon from 'app/modules/ui/icons/repeat'; -import Colors from 'app/constants/Colors'; -import { Stores } from 'app/functions/Stores'; -import cursorPointer from 'app/constants/CursorPointer'; +import LoopIcon from 'app/src/ui/icons/repeat'; +import Colors from 'app/src/constants/Colors'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import { PlayerStore } from 'app/src/stores/player'; -export default observer(({ size = 20 }) => { - let { playerStore } = Stores(); +export default ({ size = 20 }) => { + const { toggleLoop, loop } = PlayerStore(); - if (playerStore.loop) { + if (loop) { return ( playerStore.toggleLoop()} + onPress={() => toggleLoop()} > @@ -22,12 +21,12 @@ export default observer(({ size = 20 }) => { return ( playerStore.toggleLoop()} + onPress={() => toggleLoop()} > ); -}); +}; var styles = StyleSheet.create({ controlTouch: { diff --git a/modules/player/player-screen/play-btn/PlayBtn.tsx b/src/player/player-screen/play-btn/PlayBtn.tsx similarity index 71% rename from modules/player/player-screen/play-btn/PlayBtn.tsx rename to src/player/player-screen/play-btn/PlayBtn.tsx index 1e3738d..8622dc8 100644 --- a/modules/player/player-screen/play-btn/PlayBtn.tsx +++ b/src/player/player-screen/play-btn/PlayBtn.tsx @@ -1,23 +1,22 @@ import React from 'react'; import { StyleSheet, View, Pressable } from 'react-native'; -import { observer } from 'mobx-react'; import { ReplayIcon, Spinner, -} from 'app/modules/player/player-screen/video-player/VideoIcons'; -import { Stores } from 'app/functions/Stores'; -import cursorPointer from 'app/constants/CursorPointer'; -import PlayIcon from 'app/modules/ui/icons/play'; -import PauseIcon from 'app/modules/ui/icons/pause'; +} from 'app/src/player/player-screen/video-player/VideoIcons'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import PlayIcon from 'app/src/ui/icons/play'; +import PauseIcon from 'app/src/ui/icons/pause'; +import { PlayerStore } from 'app/src/stores/player'; -export default observer(() => { - let { playerStore } = Stores(); +export default () => { + let { togglePlay, playbackState, replay } = PlayerStore(); - if (playerStore.playbackState === 'PAUSED') { + if (playbackState === 'PAUSED') { return ( playerStore.togglePlay()} + onPress={() => togglePlay()} > @@ -27,11 +26,11 @@ export default observer(() => { ); } - if (playerStore.playbackState === 'PLAYING') { + if (playbackState === 'PLAYING') { return ( playerStore.togglePlay()} + onPress={() => togglePlay()} > @@ -41,11 +40,11 @@ export default observer(() => { ); } - if (playerStore.playbackState === 'ENDED') { + if (playbackState === 'ENDED') { return ( playerStore.replay()} + onPress={() => replay()} > @@ -62,7 +61,7 @@ export default observer(() => { ); -}); +}; var styles = StyleSheet.create({ controlTouch: { diff --git a/modules/player/player-screen/play-btn/PlayDesktopBtn.tsx b/src/player/player-screen/play-btn/PlayDesktopBtn.tsx similarity index 71% rename from modules/player/player-screen/play-btn/PlayDesktopBtn.tsx rename to src/player/player-screen/play-btn/PlayDesktopBtn.tsx index 765dc8e..aa2564a 100644 --- a/modules/player/player-screen/play-btn/PlayDesktopBtn.tsx +++ b/src/player/player-screen/play-btn/PlayDesktopBtn.tsx @@ -1,24 +1,23 @@ import React from 'react'; import { StyleSheet, View, Pressable } from 'react-native'; -import { observer } from 'mobx-react'; -import PlayIcon from 'app/modules/ui/icons/play'; -import PauseIcon from 'app/modules/ui/icons/pause'; +import PlayIcon from 'app/src/ui/icons/play'; +import PauseIcon from 'app/src/ui/icons/pause'; import { ReplayIcon, Spinner, -} from 'app/modules/player/player-screen/video-player/VideoIcons'; -import { Stores } from 'app/functions/Stores'; -import cursorPointer from 'app/constants/CursorPointer'; +} from 'app/src/player/player-screen/video-player/VideoIcons'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import { PlayerStore } from 'app/src/stores/player'; -export default observer(({ size = 28 }) => { - let { playerStore } = Stores(); +export default ({ size = 28 }) => { + let { togglePlay, playbackState, replay } = PlayerStore(); - if (playerStore.playbackState === 'PAUSED') { + if (playbackState === 'PAUSED') { return ( playerStore.togglePlay()} + onPress={() => togglePlay()} > @@ -28,11 +27,11 @@ export default observer(({ size = 28 }) => { ); } - if (playerStore.playbackState === 'PLAYING') { + if (playbackState === 'PLAYING') { return ( playerStore.togglePlay()} + onPress={() => togglePlay()} > @@ -42,11 +41,11 @@ export default observer(({ size = 28 }) => { ); } - if (playerStore.playbackState === 'ENDED') { + if (playbackState === 'ENDED') { return ( playerStore.replay()} + onPress={() => replay()} > @@ -63,7 +62,7 @@ export default observer(({ size = 28 }) => { ); -}); +}; var styles = StyleSheet.create({ controlTouch: { diff --git a/modules/player/player-screen/player-controls/PlayerControls.tsx b/src/player/player-screen/player-controls/PlayerControls.tsx similarity index 61% rename from modules/player/player-screen/player-controls/PlayerControls.tsx rename to src/player/player-screen/player-controls/PlayerControls.tsx index af685ed..6c04e22 100644 --- a/modules/player/player-screen/player-controls/PlayerControls.tsx +++ b/src/player/player-screen/player-controls/PlayerControls.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import PrevBtn from 'app/modules/player/player-screen/prev-btn/PrevBtn'; -import ForwardBtn from 'app/modules/player/player-screen/forward-btn/ForwardBtn'; -import ShuffleBtn from 'app/modules/player/player-screen/shuffle-btn/ShuffleBtn'; -import LoopBtn from 'app/modules/player/player-screen/loop-btn/LoopBtn'; -import PlayBtn from 'app/modules/player/player-screen/play-btn/PlayBtn'; +import PrevBtn from 'app/src/player/player-screen/prev-btn/PrevBtn'; +import ForwardBtn from 'app/src/player/player-screen/forward-btn/ForwardBtn'; +import ShuffleBtn from 'app/src/player/player-screen/shuffle-btn/ShuffleBtn'; +import LoopBtn from 'app/src/player/player-screen/loop-btn/LoopBtn'; +import PlayBtn from 'app/src/player/player-screen/play-btn/PlayBtn'; import { StyleSheet, View } from 'react-native'; diff --git a/src/player/player-screen/prev-btn/PrevBtn.tsx b/src/player/player-screen/prev-btn/PrevBtn.tsx new file mode 100644 index 0000000..98b0524 --- /dev/null +++ b/src/player/player-screen/prev-btn/PrevBtn.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { StyleSheet, Pressable } from 'react-native'; +import SkipBackwardIcon from 'app/src/ui/icons/skip-backward'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import { PlayerStore } from 'app/src/stores/player'; + +const PrevBtn = ({ size = 24 }) => { + const { playPrev } = PlayerStore(); + return ( + playPrev()} + > + + + ); +}; + +export default PrevBtn; + +var styles = StyleSheet.create({ + controlTouch: { + alignSelf: 'center', + }, + prevBtn: { + width: 24, + height: 18, + }, +}); diff --git a/modules/player/player-screen/shuffle-btn/ShuffleBtn.tsx b/src/player/player-screen/shuffle-btn/ShuffleBtn.tsx similarity index 56% rename from modules/player/player-screen/shuffle-btn/ShuffleBtn.tsx rename to src/player/player-screen/shuffle-btn/ShuffleBtn.tsx index e204d97..bb81276 100644 --- a/modules/player/player-screen/shuffle-btn/ShuffleBtn.tsx +++ b/src/player/player-screen/shuffle-btn/ShuffleBtn.tsx @@ -1,16 +1,12 @@ import React from 'react'; import { StyleSheet, Pressable } from 'react-native'; -import { inject } from 'mobx-react'; -import * as stores from 'app/skyhitz-common'; -import ShuffleIcon from 'app/modules/ui/icons/shuffle'; -import Colors from 'app/constants/Colors'; -import cursorPointer from 'app/constants/CursorPointer'; -type Stores = typeof stores; +import ShuffleIcon from 'app/src/ui/icons/shuffle'; +import Colors from 'app/src/constants/Colors'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import { PlayerStore } from 'app/src/stores/player'; -const ShuffleBtn = inject((stores: Stores) => ({ - toggleShuffle: stores.playerStore.toggleShuffle.bind(stores.playerStore), - shuffle: stores.playerStore.shuffle, -}))(({ toggleShuffle, shuffle, size = 20 }: any) => { +const ShuffleBtn = ({ size = 20 }) => { + const { toggleShuffle, shuffle } = PlayerStore(); if (shuffle) { return ( ({ ); -}); +}; export default ShuffleBtn; diff --git a/modules/player/player-screen/slider/Slider.tsx b/src/player/player-screen/slider/Slider.tsx similarity index 55% rename from modules/player/player-screen/slider/Slider.tsx rename to src/player/player-screen/slider/Slider.tsx index f7876bc..6e98610 100644 --- a/modules/player/player-screen/slider/Slider.tsx +++ b/src/player/player-screen/slider/Slider.tsx @@ -1,19 +1,25 @@ import React from 'react'; -import { observer } from 'mobx-react'; -import Colors from 'app/constants/Colors'; -import { Stores } from 'app/functions/Stores'; +import Colors from 'app/src/constants/Colors'; import { Pressable } from 'react-native'; import Slider from '@react-native-community/slider'; +import { PlayerStore } from 'app/src/stores/player'; -export default observer(() => { - const { playerStore } = Stores(); +export default () => { + const { + onSeekBarTap, + onSliderLayout, + seekPosition, + setSliding, + onSeekSliderValueChange, + onSeekSliderSlidingComplete, + } = PlayerStore(); return ( { - playerStore.onSeekBarTap(evt); + onSeekBarTap(evt); }} onLayout={(evt) => { - playerStore.onSliderLayout(evt); + onSliderLayout(evt); }} style={{ zIndex: 15, @@ -24,14 +30,14 @@ export default observer(() => { style={{ flex: 1 }} minimumValue={0} maximumValue={1} - value={playerStore.seekPosition} + value={seekPosition} onSlidingStart={(_) => { - playerStore.setSliding(true); - playerStore.onSeekSliderValueChange(); + setSliding(true); + onSeekSliderValueChange(); }} onSlidingComplete={(value) => { - playerStore.setSliding(false); - playerStore.onSeekSliderSlidingComplete(value); + setSliding(false); + onSeekSliderSlidingComplete(value); }} minimumTrackTintColor={Colors.brandBlue} maximumTrackTintColor={Colors.backgroundTrackColor} @@ -39,4 +45,4 @@ export default observer(() => { /> ); -}); +}; diff --git a/modules/player/player-screen/video-player/BlurImageBackground.tsx b/src/player/player-screen/video-player/BlurImageBackground.tsx similarity index 100% rename from modules/player/player-screen/video-player/BlurImageBackground.tsx rename to src/player/player-screen/video-player/BlurImageBackground.tsx diff --git a/modules/player/player-screen/video-player/CenteredView.tsx b/src/player/player-screen/video-player/CenteredView.tsx similarity index 82% rename from modules/player/player-screen/video-player/CenteredView.tsx rename to src/player/player-screen/video-player/CenteredView.tsx index 0db0544..186f494 100644 --- a/modules/player/player-screen/video-player/CenteredView.tsx +++ b/src/player/player-screen/video-player/CenteredView.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Animated } from 'react-native'; -import { centeredContentWidth } from 'app/modules/player/player-screen/video-player/VideoConstants'; +import { centeredContentWidth } from 'app/src/player/player-screen/video-player/VideoConstants'; const CenteredView = ({ children, ...otherProps }: any) => ( { + const { + fullscreen, + presentFullscreenPlayer, + dismissFullscreenPlayer, + } = PlayerStore(); + return ( + { + fullscreen ? dismissFullscreenPlayer() : presentFullscreenPlayer(); + }} + > + {fullscreen ? : } + + ); +}; + +export default FullscreenControl; diff --git a/modules/player/player-screen/video-player/PlayPauseInvisibleArea.tsx b/src/player/player-screen/video-player/PlayPauseInvisibleArea.tsx similarity index 60% rename from modules/player/player-screen/video-player/PlayPauseInvisibleArea.tsx rename to src/player/player-screen/video-player/PlayPauseInvisibleArea.tsx index f8745d6..e971e6e 100644 --- a/modules/player/player-screen/video-player/PlayPauseInvisibleArea.tsx +++ b/src/player/player-screen/video-player/PlayPauseInvisibleArea.tsx @@ -1,22 +1,17 @@ import React from 'react'; import { TouchableWithoutFeedback, View } from 'react-native'; -import { inject } from 'mobx-react'; import { PLAYBACK_STATES, SEEK_STATES, -} from 'app/modules/player/player-screen/video-player/UiStates'; +} from 'app/src/player/player-screen/video-player/UiStates'; import { videoHeight, videoWidth, -} from 'app/modules/player/player-screen/video-player/VideoConstants'; -import * as stores from 'app/skyhitz-common'; -type Stores = typeof stores; +} from 'app/src/player/player-screen/video-player/VideoConstants'; +import { PlayerStore } from 'app/src/stores/player'; -const PlayPauseInvisibleArea = inject((stores: Stores) => ({ - playbackState: stores.playerStore.playbackState, - seekState: stores.playerStore.seekState, - togglePlay: stores.playerStore.togglePlay.bind(stores.playerStore), -}))(({ playbackState, seekState, togglePlay }: any) => { +const PlayPauseInvisibleArea = () => { + const { playbackState, seekState, togglePlay } = PlayerStore(); if ( (seekState == SEEK_STATES.NOT_SEEKING || seekState == SEEK_STATES.SEEKED) && (playbackState == PLAYBACK_STATES.PLAYING || @@ -39,6 +34,6 @@ const PlayPauseInvisibleArea = inject((stores: Stores) => ({ ); } return null; -}); +}; export default PlayPauseInvisibleArea; diff --git a/modules/player/player-screen/video-player/UiStates.tsx b/src/player/player-screen/video-player/UiStates.tsx similarity index 100% rename from modules/player/player-screen/video-player/UiStates.tsx rename to src/player/player-screen/video-player/UiStates.tsx diff --git a/modules/player/player-screen/video-player/VideoComponent.tsx b/src/player/player-screen/video-player/VideoComponent.tsx similarity index 71% rename from modules/player/player-screen/video-player/VideoComponent.tsx rename to src/player/player-screen/video-player/VideoComponent.tsx index 622328c..fc99d35 100644 --- a/modules/player/player-screen/video-player/VideoComponent.tsx +++ b/src/player/player-screen/video-player/VideoComponent.tsx @@ -1,6 +1,5 @@ +import { PlayerStore } from 'app/src/stores/player'; import { Video, Audio } from 'expo-av'; -import { observer } from 'mobx-react'; -import { Stores } from 'app/functions/Stores'; import { useState } from 'react'; import tw from 'twin.macro'; import BlurImageBackground from './BlurImageBackground'; @@ -15,8 +14,15 @@ Audio.setAudioModeAsync({ playThroughEarpieceAndroid: false, }); -export default observer(({ desktop = false }) => { - let { playerStore } = Stores(); +export default ({ desktop = false }) => { + const { + entry, + streamUrl, + onPlaybackStatusUpdate, + mountVideo, + onFullscreenUpdate, + onError, + } = PlayerStore(); const [loading, setLoading] = useState(false); const [hasVideo, setHasVideo] = useState(false); @@ -25,22 +31,22 @@ export default observer(({ desktop = false }) => { image={ loading || !hasVideo ? desktop - ? playerStore.entry?.imageUrlSmall - : playerStore.entry?.imageSrc + ? entry?.imageUrlSmall + : entry?.imageSrc : null } - style={[playerStore.streamUrl && tw`bg-blue-dark`]} + style={[streamUrl && tw`bg-blue-dark`]} intensity={0} > ); -}); +}; let styles = StyleSheet.create({ rowWrap: { diff --git a/src/playlists/LikesScreen.tsx b/src/playlists/LikesScreen.tsx new file mode 100644 index 0000000..15d15c1 --- /dev/null +++ b/src/playlists/LikesScreen.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { ScrollView } from 'react-native'; +import EntryRow from 'app/src/ui/EntryRow'; +import SearchingLoader from 'app/src/ui/SearchingLoader'; +import Colors from 'app/src/constants/Colors'; +import BottomPlaceholder from 'app/src/ui/BottomPlaceholder'; +import ResponsiveLayout from '../ui/ResponsiveLayout'; +import { LikesStore } from '../stores/likes'; + +export default () => { + const { userLikes, loading } = LikesStore(); + const loadAndPlay = () => {}; + return ( + + + {SearchingLoader(loading)} + {userLikes.map((entry) => ( + + ))} + + + + ); +}; diff --git a/modules/playlists/MyMusicRow.tsx b/src/playlists/MyMusicRow.tsx similarity index 79% rename from modules/playlists/MyMusicRow.tsx rename to src/playlists/MyMusicRow.tsx index cf11800..41660fc 100644 --- a/modules/playlists/MyMusicRow.tsx +++ b/src/playlists/MyMusicRow.tsx @@ -1,16 +1,14 @@ import React from 'react'; import { StyleSheet, View, Text, Pressable } from 'react-native'; -import { observer } from 'mobx-react'; - -import Colors from 'app/constants/Colors'; -import { Stores } from 'app/functions/Stores'; +import Colors from 'app/src/constants/Colors'; import { useLinkTo } from '@react-navigation/native'; -import cursorPointer from 'app/constants/CursorPointer'; -import ChevronRightIcon from 'app/modules/ui/icons/chevron-right'; -import StarBorderIcon from 'app/modules/ui/icons/star-border'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import ChevronRightIcon from 'app/src/ui/icons/chevron-right'; +import StarBorderIcon from 'app/src/ui/icons/star-border'; +import { UserEntriesStore } from '../stores/user-entries'; -export default observer(() => { - let { userEntriesStore } = Stores(); +export default () => { + let { entries } = UserEntriesStore(); let linkTo = useLinkTo(); const handleNavigation = () => { @@ -18,10 +16,10 @@ export default observer(() => { }; const copy = () => { - if (!userEntriesStore.entriesCount) { + if (!entries.length) { return null; } - return `${userEntriesStore.entriesCount}`; + return `${entries.length}`; }; return ( @@ -41,7 +39,7 @@ export default observer(() => { ); -}); +}; let styles = StyleSheet.create({ rowWrap: { diff --git a/src/profile/EditProfilePhotoBtn.tsx b/src/profile/EditProfilePhotoBtn.tsx new file mode 100644 index 0000000..ab372c5 --- /dev/null +++ b/src/profile/EditProfilePhotoBtn.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { Pressable, StyleSheet, Text, Platform } from 'react-native'; +import * as ImagePicker from 'expo-image-picker'; +import * as Permissions from 'expo-permissions'; +import { useSetRecoilState } from 'recoil'; +import Colors from 'app/src/constants/Colors'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import { imagesGateway, nftStorageApi } from '../config/constants'; +import { profileAtom } from '../atoms/atoms'; + +export default () => { + const setEditProfile = useSetRecoilState(profileAtom); + + const uploadProfilePhoto = async (image) => { + const isPng = image.uri.startsWith('data:image/png'); + if (!isPng) { + setEditProfile((oldState) => ({ + ...oldState, + uploadError: 'Only png files supported!', + })); + return; + } + if (image.height !== image.width) { + setEditProfile((oldState) => ({ + ...oldState, + uploadError: 'Only square images supported!', + })); + return; + } + const blobRes = await fetch(image.uri); + const file = await blobRes.blob(); + setEditProfile((oldState) => ({ ...oldState, loadingAvatar: true })); + let res = await fetch(`${nftStorageApi}/upload`, { + method: 'POST', + body: file, + headers: new Headers({ + Authorization: `Bearer ${process.env.NEXT_PUBLIC_NFT_STORAGE_API_KEY}`, + }), + }); + let { value, ok } = await res.json(); + + if (ok) { + setEditProfile((oldState) => ({ + ...oldState, + avatarUrl: `${imagesGateway}/${value.cid}`, + loadingAvatar: false, + })); + return; + } + setEditProfile((oldState) => ({ ...oldState, loadingAvatar: false })); + }; + + const launchImageLibrary = async () => { + let image = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ImagePicker.MediaTypeOptions.Images, + allowsEditing: true, + aspect: [1, 1], + quality: 0.7, + base64: true, + exif: true, + }); + if (image && !image.cancelled) { + uploadProfilePhoto(image); + } + }; + + const changeProfilePhoto = async () => { + if (Platform.OS === 'ios' || Platform.OS === 'android') { + const { status } = await Permissions.askAsync(Permissions.CAMERA_ROLL); + if (status === 'granted') { + return launchImageLibrary(); + } + } + launchImageLibrary(); + }; + + return ( + + Change Profile Photo + + ); +}; + +const styles = StyleSheet.create({ + btn: { + marginBottom: 20, + }, +}); diff --git a/src/profile/EditProfileScreen.tsx b/src/profile/EditProfileScreen.tsx new file mode 100644 index 0000000..8245503 --- /dev/null +++ b/src/profile/EditProfileScreen.tsx @@ -0,0 +1,347 @@ +import React from 'react'; +import { + View, + StyleSheet, + Text, + TextInput, + Pressable, + Platform, + KeyboardAvoidingView, +} from 'react-native'; +import Colors from 'app/src/constants/Colors'; +import { + UserAvatarMedium, + UserAvatarMediumWithUrlOnly, + LoadingUserAvatar, +} from 'app/src/ui/UserAvatar'; +import EditProfilePhotoBtn from 'app/src/profile/EditProfilePhotoBtn'; +import LargeBtn from '../ui/LargeBtn'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import AccountBoxIcon from 'app/src/ui/icons/account-box'; +import PersonOutlineIcon from 'app/src/ui/icons/person-outline'; +import MailOutlineIcon from 'app/src/ui/icons/mail-outline'; +import LogoutIcon from 'app/src/ui/icons/logout'; +import InfoCirlceIcon from 'app/src/ui/icons/info-circle'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { SessionStore } from '../stores/session'; +import { profileAtom, profileValidationErrorAtom } from '../atoms/atoms'; +import { useRecoilState, useRecoilValue } from 'recoil'; + +export default (props) => { + const { signOut } = SessionStore(); + const [ + { loadingAvatar, avatarUrl, displayName, username, email, description }, + setEditProfile, + ] = useRecoilState(profileAtom); + + const validationError = useRecoilValue(profileValidationErrorAtom); + + const handleLogOut = async () => { + await signOut(); + await AsyncStorage.multiRemove(await AsyncStorage.getAllKeys()); + props.navigation.navigate(Platform.OS === 'web' ? 'WebApp' : 'AuthScreen'); + }; + + const handleWithdrawal = async () => { + props.navigation.navigate('WithdrawalModal'); + }; + + // TODO: fix props.profile with initials + const renderAvatar = () => { + if (loadingAvatar) { + return ; + } + if (avatarUrl) { + return UserAvatarMediumWithUrlOnly(avatarUrl); + } + return UserAvatarMedium(props.profile); + }; + + // TODO: get credits from payments store + const renderWithdrawalXLM = () => { + if (props.credits && props.credits > 0) { + return ( + + Credits + + + ); + } + return null; + }; + + return ( + + + + {validationError} + + + {renderAvatar()} + + + + + + + + + + setEditProfile((oldState) => ({ + ...oldState, + displayName: text, + })) + } + maxLength={30} + /> + + + + + + + setEditProfile((oldState) => ({ + ...oldState, + description: text, + })) + } + maxLength={150} + /> + + + + + + + setEditProfile((oldState) => ({ + ...oldState, + username: text, + })) + } + maxLength={30} + /> + + + Private Information + + + + + + + + setEditProfile((oldState) => ({ + ...oldState, + email: text, + })) + } + maxLength={34} + /> + + + {renderWithdrawalXLM()} + More + + + + + + + Log Out + + + + + + ); +}; + +const formPadding = 20; +const maxHeight = 50; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: Colors.listItemBackground, + }, + headerWrap: { + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }, + inputContainerTop: { + paddingLeft: formPadding, + paddingRight: formPadding, + marginTop: 0, + flex: 1, + maxHeight: maxHeight * 3, + borderBottomColor: Colors.transparent, + borderBottomWidth: 1, + borderTopColor: Colors.transparent, + borderTopWidth: 1, + }, + inputContainerMiddle: { + paddingLeft: formPadding, + paddingRight: formPadding, + marginTop: 0, + flex: 1, + maxHeight: maxHeight, + borderBottomColor: Colors.transparent, + borderBottomWidth: 1, + borderTopColor: Colors.transparent, + borderTopWidth: 1, + }, + inputContainerBottom: { + paddingLeft: formPadding, + paddingRight: formPadding, + marginTop: 0, + width: '100%', + height: maxHeight, + borderBottomColor: Colors.transparent, + borderBottomWidth: 1, + borderTopColor: Colors.transparent, + borderTopWidth: 1, + flexDirection: 'row', + alignItems: 'center', + }, + withdrawalContainer: { + paddingLeft: formPadding, + paddingRight: formPadding, + marginTop: 0, + flex: 1, + height: 50, + minHeight: 50, + borderBottomColor: Colors.transparent, + borderBottomWidth: 1, + borderTopColor: Colors.transparent, + borderTopWidth: 1, + }, + input: { + backgroundColor: Colors.transparent, + color: Colors.defaultTextLight, + fontSize: 14, + paddingLeft: 36, + }, + withdrawInput: { + backgroundColor: Colors.transparent, + color: Colors.defaultTextLight, + fontSize: 14, + paddingLeft: 36, + bottom: 0, + }, + field: { + height: maxHeight, + borderBottomColor: Colors.dividerBackground, + borderBottomWidth: 0.5, + justifyContent: 'flex-start', + width: '100%', + flexDirection: 'row', + alignItems: 'center', + }, + withdrawalXLMField: { + paddingLeft: 18, + height: 80, + width: '100%', + justifyContent: 'center', + paddingBottom: 10, + marginTop: 20, + }, + fieldWithoutBorder: { + height: maxHeight, + justifyContent: 'flex-start', + width: '100%', + flexDirection: 'row', + alignItems: 'center', + }, + placeholderIcon: { + position: 'absolute', + left: 0, + backgroundColor: Colors.transparent, + }, + coinIcon: { + position: 'absolute', + left: 20, + backgroundColor: Colors.transparent, + }, + privateInfo: { + paddingLeft: 18, + paddingTop: 30, + paddingBottom: 5, + fontSize: 14, + fontWeight: 'bold', + color: Colors.defaultTextLight, + }, + creditsInfo: { + paddingTop: 20, + paddingBottom: 20, + fontSize: 14, + fontWeight: 'bold', + color: Colors.defaultTextLight, + }, + errorContainer: { + maxHeight: 40, + backgroundColor: Colors.errorBackground, + paddingLeft: formPadding, + paddingRight: formPadding, + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + error: { + color: Colors.white, + }, +}); diff --git a/modules/profile/LowBalanceModal.tsx b/src/profile/LowBalanceModal.tsx similarity index 88% rename from modules/profile/LowBalanceModal.tsx rename to src/profile/LowBalanceModal.tsx index 75dc0ad..9fd6b6c 100644 --- a/modules/profile/LowBalanceModal.tsx +++ b/src/profile/LowBalanceModal.tsx @@ -7,18 +7,20 @@ import { TextInput, Platform, } from 'react-native'; -import { observer } from 'mobx-react'; -import Colors from 'app/constants/Colors'; -import LargeBtn from 'app/modules/ui/LargeBtn'; -import { Stores } from 'app/functions/Stores'; +import Colors from 'app/src/constants/Colors'; +import LargeBtn from 'app/src/ui/LargeBtn'; import { useLinkTo, useNavigation } from '@react-navigation/native'; -import cursorPointer from 'app/constants/CursorPointer'; -import CloseIcon from 'app/modules/ui/icons/x'; -import WalletIcon from 'app/modules/ui/icons/wallet'; -import DollarIcon from 'app/modules/ui/icons/dollar'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import CloseIcon from 'app/src/ui/icons/x'; +import WalletIcon from 'app/src/ui/icons/wallet'; +import DollarIcon from 'app/src/ui/icons/dollar'; +import { userAtom } from '../atoms/atoms'; +import { useRecoilValue } from 'recoil'; +import { PaymentsStore } from '../stores/payments'; -export default observer((props) => { - const { paymentsStore, sessionStore } = Stores(); +export default (props) => { + const { credits } = PaymentsStore(); + const user = useRecoilValue(userAtom); const { goBack } = useNavigation(); const linkTo = useLinkTo(); @@ -48,7 +50,7 @@ export default observer((props) => { {' '} - {paymentsStore.credits.toFixed(4)} + {credits.toFixed(4)} @@ -80,7 +82,7 @@ export default observer((props) => { Platform.OS === 'web' ? ({ outlineWidth: 0 } as any) : {}, ]} placeholderTextColor="white" - value={sessionStore.user?.publicKey} + value={user?.publicKey} maxLength={56} /> @@ -91,7 +93,7 @@ export default observer((props) => { ); -}); +}; const styles = StyleSheet.create({ fieldWithoutBorderTop: { diff --git a/modules/profile/MediaUpload.tsx b/src/profile/MediaUpload.tsx similarity index 76% rename from modules/profile/MediaUpload.tsx rename to src/profile/MediaUpload.tsx index a150ddf..dda9523 100644 --- a/modules/profile/MediaUpload.tsx +++ b/src/profile/MediaUpload.tsx @@ -10,24 +10,25 @@ import { Alert, ActivityIndicator, } from 'react-native'; -import { observer } from 'mobx-react'; import * as ImagePicker from 'expo-image-picker'; import * as DocumentPicker from 'expo-document-picker'; import * as Permissions from 'expo-permissions'; -import Colors from 'app/constants/Colors'; -import LargeBtn from 'app/modules/ui/LargeBtn'; -import cursorPointer from 'app/constants/CursorPointer'; -import { Stores } from 'app/functions/Stores'; +import Colors from 'app/src/constants/Colors'; +import LargeBtn from 'app/src/ui/LargeBtn'; +import cursorPointer from 'app/src/constants/CursorPointer'; import { useLinkTo } from '@react-navigation/native'; import Slider from '@react-native-community/slider'; -import PieChartIcon from 'app/modules/ui/icons/pie'; -import InfoIcon from 'app/modules/ui/icons/info-circle'; -import DollarIcon from 'app/modules/ui/icons/dollar'; -import CircleIcon from 'app/modules/ui/icons/circle'; -import UploadIcon from 'app/modules/ui/icons/upload'; -import CheckIcon from 'app/modules/ui/icons/check'; -import CloseIcon from 'app/modules/ui/icons/x'; -import nftListener from 'app/hooks/nft-listener'; +import PieChartIcon from 'app/src/ui/icons/pie'; +import InfoIcon from 'app/src/ui/icons/info-circle'; +import DollarIcon from 'app/src/ui/icons/dollar'; +import CircleIcon from 'app/src/ui/icons/circle'; +import UploadIcon from 'app/src/ui/icons/upload'; +import CheckIcon from 'app/src/ui/icons/check'; +import CloseIcon from 'app/src/ui/icons/x'; +import nftListener from 'app/src/hooks/nft-listener'; +import { UserEntriesStore } from '../stores/user-entries'; +import { EntryStore } from '../stores/entry'; +import { WalletConnectStore } from '../stores/wallet-connect'; const SwitchWeb: any = Switch; @@ -74,14 +75,45 @@ const ArtworkSection = ({ imageSelected, selectArtwork }) => { return ; }; -export default observer(() => { - const { entryStore, userEntriesStore, walletConnectStore } = Stores(); +export default () => { + const { signAndSubmitXdr } = WalletConnectStore(); + const { + equityForSale, + setUploadingError, + create, + canCreate, + setLoadingVideo, + setImageBlob, + setVideoBlob, + clearStore, + uploadingError, + clearUploadingError, + setArtist, + artist, + title, + setTitle, + availableForSale, + setAvailableForSale, + description, + setDescription, + setPrice, + price, + setEquityForSale, + equityForSalePercentage, + imageBlob, + videoBlob, + creating, + currentUpload, + loadingVideo, + progress, + } = EntryStore(); + const { refreshEntries } = UserEntriesStore(); const linkTo = useLinkTo(); const [openListener, setOpenListener] = useState(false); const { indexed } = nftListener(openListener); - let equityForSaleValue = entryStore.equityForSale; + let equityForSaleValue = equityForSale; const getPermissionAsync = async () => { if (Platform.OS === 'ios' || Platform.OS === 'android') { const { status } = await Permissions.askAsync(Permissions.MEDIA_LIBRARY); @@ -116,21 +148,21 @@ export default observer(() => { }); } catch (e) { console.log('error', e); - entryStore.setUploadingError('Error picking file!'); + setUploadingError('Error picking file!'); } if (video && !video.cancelled) { - entryStore.setLoadingVideo(true); + setLoadingVideo(true); // const isMp4 = video.uri.startsWith('data:video/mp4'); // if (!isMp4) { - // entryStore.setUploadingError('Only mp4 files supported!'); + // setUploadingError('Only mp4 files supported!'); // return; // } const res = await fetch(video.uri); const file = await res.blob(); - entryStore.setLoadingVideo(false); - entryStore.setVideoBlob(file); + setLoadingVideo(false); + setVideoBlob(file); } }; @@ -146,26 +178,24 @@ export default observer(() => { if (image && !image.cancelled) { const isPng = image.uri.startsWith('data:image/png'); if (!isPng) { - entryStore.setUploadingError('Only png files supported!'); + setUploadingError('Only png files supported!'); return; } if (image.height !== image.width) { - return entryStore.setUploadingError('Only square images supported!'); + return setUploadingError('Only square images supported!'); } if (image.width < 3000) { - return entryStore.setUploadingError( - 'Image should be at least 3000px wide!' - ); + return setUploadingError('Image should be at least 3000px wide!'); } const res = await fetch(image.uri); const file = await res.blob(); - entryStore.setImageBlob(file); + setImageBlob(file); } }; const handleIndexedEntry = async () => { - await userEntriesStore.refreshEntries(); - entryStore.clearStore(); + await refreshEntries(); + clearStore(); linkTo('/dashboard/profile'); }; @@ -179,24 +209,20 @@ export default observer(() => { const onCreate = async () => { setOpenListener(true); - const res = await entryStore.create(); + const res = await create(); if (!res) { - return entryStore.setUploadingError( - 'Something went wrong. Please try again!' - ); + return setUploadingError('Something went wrong. Please try again!'); } let { xdr, submitted, success } = res; if (!success) { - return entryStore.setUploadingError( - 'Something went very wrong. Please contact us!' - ); + return setUploadingError('Something went very wrong. Please contact us!'); } if (!submitted) { let message; try { - message = await walletConnectStore.signAndSubmitXdr(xdr); + message = await signAndSubmitXdr(xdr); } catch (e) { console.log(e); } @@ -206,11 +232,11 @@ export default observer(() => { return; }; - if (entryStore.uploadingError) { + if (uploadingError) { return ( entryStore.clearUploadingError()} + uploadingError={uploadingError} + clearUploadingError={() => clearUploadingError()} /> ); } @@ -230,8 +256,8 @@ export default observer(() => { Platform.OS === 'web' ? ({ outlineWidth: 0 } as any) : {}, ]} placeholderTextColor="white" - value={entryStore.artist} - onChangeText={(t) => entryStore.updateArtist(t)} + value={artist} + onChangeText={(t) => setArtist(t)} maxLength={34} /> @@ -247,8 +273,8 @@ export default observer(() => { Platform.OS === 'web' ? ({ outlineWidth: 0 } as any) : {}, ]} placeholderTextColor="white" - value={entryStore.title} - onChangeText={(t) => entryStore.updateTitle(t)} + value={title} + onChangeText={(t) => setTitle(t)} maxLength={34} /> @@ -264,8 +290,8 @@ export default observer(() => { Platform.OS === 'web' ? ({ outlineWidth: 0 } as any) : {}, ]} placeholderTextColor="white" - value={entryStore.description} - onChangeText={(t) => entryStore.updateDescription(t)} + value={description} + onChangeText={(t) => setDescription(t)} maxLength={60} /> @@ -273,9 +299,7 @@ export default observer(() => { { {'Available for Sale: '} - entryStore.updateAvailableForSale(forSale) - } - value={entryStore.availableForSale} + onValueChange={(forSale) => setAvailableForSale(forSale)} + value={availableForSale} style={[ styles.switch, Platform.OS === 'web' ? ({ outlineWidth: 0 } as any) : {}, @@ -302,7 +324,7 @@ export default observer(() => { thumbColor={Colors.defaultTextLight} /> - {entryStore.availableForSale && ( + {availableForSale && ( <> @@ -326,8 +348,8 @@ export default observer(() => { Platform.OS === 'web' ? ({ outlineWidth: 0 } as any) : {}, ]} placeholderTextColor="white" - value={entryStore.price ? entryStore.price.toString() : ''} - onChangeText={(price) => entryStore.updatePrice(parseInt(price))} + value={price ? price.toString() : ''} + onChangeText={(price) => setPrice(parseInt(price))} maxLength={30} /> @@ -339,7 +361,7 @@ export default observer(() => { numberOfLines={1} > {'Equity for Sale: '} - {entryStore.equityForSalePercentage} + {equityForSalePercentage} { maximumValue={100} value={equityForSaleValue} onValueChange={(target) => { - entryStore.updateEquityForSalePercentage(target); + setEquityForSale(target); }} step={1} minimumTrackTintColor={Colors.brandBlue} @@ -368,7 +390,7 @@ export default observer(() => { {'Artwork: '} @@ -383,9 +405,9 @@ export default observer(() => { @@ -397,13 +419,13 @@ export default observer(() => { { ); -}); +}; const styles = StyleSheet.create({ wrap: { diff --git a/modules/profile/MintNFT.tsx b/src/profile/MintNFT.tsx similarity index 100% rename from modules/profile/MintNFT.tsx rename to src/profile/MintNFT.tsx diff --git a/modules/profile/PaymentModal.native.tsx b/src/profile/PaymentModal.native.tsx similarity index 87% rename from modules/profile/PaymentModal.native.tsx rename to src/profile/PaymentModal.native.tsx index bc8202f..70ca752 100644 --- a/modules/profile/PaymentModal.native.tsx +++ b/src/profile/PaymentModal.native.tsx @@ -1,19 +1,18 @@ import React from 'react'; import { Pressable, StyleSheet, Text, View } from 'react-native'; -import Colors from 'app/constants/Colors'; -import { observer } from 'mobx-react'; -import { Stores } from 'app/functions/Stores'; -import CloseIcon from 'app/modules/ui/icons/x'; +import Colors from 'app/src/constants/Colors'; +import CloseIcon from 'app/src/ui/icons/x'; +import { EntryStore } from '../stores/entry'; -export default observer((props) => { - let { entryStore } = Stores(); +export default (props) => { + let { clearUploadingError } = EntryStore(); return ( { - entryStore.clearUploadingError(); + clearUploadingError(); props.navigation.goBack(); }} > @@ -25,7 +24,7 @@ export default observer((props) => { ); -}); +}; const styles = StyleSheet.create({ btn: { diff --git a/modules/profile/PaymentModal.tsx b/src/profile/PaymentModal.tsx similarity index 85% rename from modules/profile/PaymentModal.tsx rename to src/profile/PaymentModal.tsx index 3dde8a2..960ab0b 100644 --- a/modules/profile/PaymentModal.tsx +++ b/src/profile/PaymentModal.tsx @@ -1,14 +1,13 @@ import React from 'react'; import { Pressable, StyleSheet, Text, View, Platform } from 'react-native'; -import Colors from 'app/constants/Colors'; -import { observer } from 'mobx-react'; -import { Stores } from 'app/functions/Stores'; +import Colors from 'app/src/constants/Colors'; import { Elements } from '@stripe/react-stripe-js'; import { loadStripe } from '@stripe/stripe-js'; -import { Config } from 'app/skyhitz-common/src/config/index'; +import { Config } from 'app/src/config/index'; import PaymentStep from './PaymentStep'; -import cursorPointer from 'app/constants/CursorPointer'; -import CloseIcon from 'app/modules/ui/icons/x'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import CloseIcon from 'app/src/ui/icons/x'; +import { EntryStore } from '../stores/entry'; let stripePromise; @@ -16,15 +15,15 @@ if (Platform.OS === 'web') { stripePromise = loadStripe(Config.STRIPE_PUBLISHABLE_KEY); } -export default observer((props) => { - let { entryStore } = Stores(); +export default (props) => { + let { clearUploadingError } = EntryStore(); return ( { - entryStore.clearUploadingError(); + clearUploadingError(); props.navigation.goBack(); }} > @@ -41,7 +40,7 @@ export default observer((props) => { ); -}); +}; const styles = StyleSheet.create({ btn: { diff --git a/modules/profile/PaymentStep.tsx b/src/profile/PaymentStep.tsx similarity index 81% rename from modules/profile/PaymentStep.tsx rename to src/profile/PaymentStep.tsx index baf1692..e412c1f 100644 --- a/modules/profile/PaymentStep.tsx +++ b/src/profile/PaymentStep.tsx @@ -1,7 +1,5 @@ import React, { useEffect, useState } from 'react'; import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js'; -import { observer } from 'mobx-react'; -import { Stores } from 'app/functions/Stores'; import { StyleSheet, View, @@ -11,38 +9,52 @@ import { Pressable, } from 'react-native'; import { P, H3 } from '@expo/html-elements'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; import { useNavigation } from '@react-navigation/native'; -import cursorPointer from 'app/constants/CursorPointer'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import { PaymentsStore } from '../stores/payments'; async function timeout(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } -export default observer((props) => { +export default (props) => { const [amount, setAmount] = useState(200); const stripe = useStripe(); const elements = useElements(); const { goBack } = useNavigation(); - const { paymentsStore } = Stores(); + const { + subscribed, + refreshXLMPrice, + setLoadingBalance, + refreshSubscription, + credits, + subscribeUser, + setSubmittingSubscription, + buyCredits, + xlmPriceWithFees, + subscriptionLoaded, + xlmPrice, + submittingSubscription, + } = PaymentsStore(); const [selectedOption, setSelectedOption] = useState( - paymentsStore.subscribed ? 'one-time' : 'subscription' + subscribed ? 'one-time' : 'subscription' ); useEffect(() => { - paymentsStore.refreshXLMPrice(); + refreshXLMPrice(); }, []); - const refreshSubscription = async () => { - paymentsStore.setLoadingBalance(true); - let lastBalance = paymentsStore.credits; + const handleRefreshSubscription = async () => { + setLoadingBalance(true); + let lastBalance = credits; await timeout(15000); - await paymentsStore.refreshSubscription(); - if (lastBalance === paymentsStore.credits) { - paymentsStore.setLoadingBalance(true); + await refreshSubscription(); + if (lastBalance === credits) { + setLoadingBalance(true); await timeout(15000); - paymentsStore.refreshSubscription(); + refreshSubscription(); } }; @@ -53,7 +65,7 @@ export default observer((props) => { }; const submit = async () => { - paymentsStore.setSubmittingSubscription(true); + setSubmittingSubscription(true); if (!stripe || !elements) { // Stripe.js has not loaded yet. Make sure to disable // form submission until Stripe.js has loaded. @@ -72,22 +84,19 @@ export default observer((props) => { if (!token) return; const { id } = token; if (selectedOption === 'one-time') { - const purchased = await paymentsStore.buyCredits( - id, - amount * paymentsStore.xlmPriceWithFees - ); + const purchased = await buyCredits(id, amount * xlmPriceWithFees()); if (purchased) { goBack(); - refreshSubscription(); + handleRefreshSubscription(); } return; } - const subscribed = await paymentsStore.subscribeUser(id); + const subscribed = await subscribeUser(id); if (subscribed) { goBack(); - refreshSubscription(); + handleRefreshSubscription(); } }; @@ -110,7 +119,7 @@ export default observer((props) => { setSelectedOption('subscription'); }; - if (!paymentsStore.subscriptionLoaded) { + if (!subscriptionLoaded) { return (

Checking if you already subscribed...

@@ -120,7 +129,7 @@ export default observer((props) => { return ( - {!paymentsStore.subscribed && ( + {!subscribed && ( { >

- {paymentsStore.xlmPrice && - (7.99 / paymentsStore.xlmPriceWithFees).toFixed(2)} + {xlmPrice && (7.99 / xlmPriceWithFees()).toFixed(2)}

Monthly XLM plan

@@ -167,10 +175,8 @@ export default observer((props) => {

Buy XLM

- $ - {paymentsStore.xlmPrice && - (amount * paymentsStore.xlmPriceWithFees).toFixed(2)}{' '} - one time + ${xlmPrice && (amount * xlmPriceWithFees()).toFixed(2)} one + time

@@ -193,7 +199,7 @@ export default observer((props) => { }, }} /> - {paymentsStore.submittingSubscription ? ( + {submittingSubscription ? (

Submitting Transaction...

@@ -207,7 +213,7 @@ export default observer((props) => { )}
); -}); +}; const styles = StyleSheet.create({ radioWrapper: { diff --git a/src/profile/ProfileEntryListView.tsx b/src/profile/ProfileEntryListView.tsx new file mode 100644 index 0000000..a315cd2 --- /dev/null +++ b/src/profile/ProfileEntryListView.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { ScrollView } from 'react-native'; +import EntryRow from 'app/src/ui/EntryRow'; +import SearchingLoader from 'app/src/ui/SearchingLoader'; +import Colors from 'app/src/constants/Colors'; +import BottomPlaceholder from 'app/src/ui/BottomPlaceholder'; +import ProfileStore from '../stores/profile'; + +const ProfileEntryListView = () => { + const { profileEntries, loadingEntries } = ProfileStore(); + const loadAndPlay = () => {}; + + return ( + + {SearchingLoader(loadingEntries)} + {profileEntries.map((entry: any) => ( + + ))} + + + ); +}; + +export default ProfileEntryListView; diff --git a/modules/profile/ProfileScreen.tsx b/src/profile/ProfileScreen.tsx similarity index 73% rename from modules/profile/ProfileScreen.tsx rename to src/profile/ProfileScreen.tsx index ef719f9..bb837bd 100644 --- a/modules/profile/ProfileScreen.tsx +++ b/src/profile/ProfileScreen.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { View, StyleSheet } from 'react-native'; -import ProfileTopContainer from 'app/modules/profile/ProfileTopContainer'; -import ProfileEntryListView from 'app/modules/profile/ProfileEntryListView'; -import Colors from 'app/constants/Colors'; +import ProfileTopContainer from 'app/src/profile/ProfileTopContainer'; +import ProfileEntryListView from 'app/src/profile/ProfileEntryListView'; +import Colors from 'app/src/constants/Colors'; import ResponsiveLayout from '../ui/ResponsiveLayout'; export default class ProfileScreen extends React.Component { diff --git a/modules/profile/ProfileSettingsScreen.tsx b/src/profile/ProfileSettingsScreen.tsx similarity index 76% rename from modules/profile/ProfileSettingsScreen.tsx rename to src/profile/ProfileSettingsScreen.tsx index 1d7fb02..83d27a4 100644 --- a/modules/profile/ProfileSettingsScreen.tsx +++ b/src/profile/ProfileSettingsScreen.tsx @@ -1,28 +1,32 @@ import React, { useEffect } from 'react'; import { View, StyleSheet } from 'react-native'; import { createStackNavigator } from '@react-navigation/stack'; -import { observer } from 'mobx-react'; -import Colors from 'app/constants/Colors'; -import ProfileSettingsTopContainer from 'app/modules/profile/ProfileSettingsTopContainer'; -import ShareAppBanner from 'app/modules/marketing/ShareAppBanner'; -import LikesScreen from 'app/modules/playlists/LikesScreen'; -import CollectionScreen from 'app/modules/playlists/CollectionScreen'; -import LikesRow from 'app/modules/playlists/LikesRow'; -import MyMusicRow from 'app/modules/playlists/MyMusicRow'; -import { Stores } from 'app/functions/Stores'; +import Colors from 'app/src/constants/Colors'; +import ProfileSettingsTopContainer from 'app/src/profile/ProfileSettingsTopContainer'; +import ShareAppBanner from 'app/src/marketing/ShareAppBanner'; +import LikesScreen from 'app/src/playlists/LikesScreen'; +import CollectionScreen from 'app/src/playlists/CollectionScreen'; +import LikesRow from 'app/src/playlists/LikesRow'; +import MyMusicRow from 'app/src/playlists/MyMusicRow'; import ResponsiveLayout from '../ui/ResponsiveLayout'; import MintNFT from './MintNFT'; import { useLinkTo } from '@react-navigation/native'; import { HeaderBackButton } from '@react-navigation/elements'; -import ChevronLeftIcon from 'app/modules/ui/icons/chevron-left'; +import ChevronLeftIcon from 'app/src/ui/icons/chevron-left'; import tw from 'twin.macro'; +import { UserEntriesStore } from '../stores/user-entries'; +import { LikesStore } from '../stores/likes'; +import { PaymentsStore } from '../stores/payments'; + +const ProfileSettingsScreen = () => { + let { refreshSubscription, credits } = PaymentsStore(); + const { refreshLikes } = LikesStore(); + const { refreshEntries } = UserEntriesStore(); -const ProfileSettingsScreen = observer(() => { - let { likesStore, userEntriesStore, paymentsStore } = Stores(); useEffect(() => { - likesStore.refreshLikes(); - userEntriesStore.refreshEntries(); - paymentsStore.refreshSubscription(); + refreshLikes(); + refreshEntries(); + refreshSubscription(); }); return ( @@ -32,12 +36,12 @@ const ProfileSettingsScreen = observer(() => { - +
); -}); +}; const ProfileSettingsStack = createStackNavigator(); diff --git a/modules/profile/ProfileSettingsTopContainer.tsx b/src/profile/ProfileSettingsTopContainer.tsx similarity index 69% rename from modules/profile/ProfileSettingsTopContainer.tsx rename to src/profile/ProfileSettingsTopContainer.tsx index d0cb5d8..00a05cd 100644 --- a/modules/profile/ProfileSettingsTopContainer.tsx +++ b/src/profile/ProfileSettingsTopContainer.tsx @@ -1,21 +1,23 @@ import React from 'react'; import { View, StyleSheet, Text } from 'react-native'; -import Colors from 'app/constants/Colors'; -import Layout from 'app/constants/Layout'; -import { UserAvatarMedium } from 'app/modules/ui/UserAvatar'; -import { observer } from 'mobx-react'; -import { Stores } from 'app/functions/Stores'; +import Colors from 'app/src/constants/Colors'; +import Layout from 'app/src/constants/Layout'; +import { UserAvatarMedium } from 'app/src/ui/UserAvatar'; import EditBtn from '../ui/EditBtn'; -import DollarIcon from 'app/modules/ui/icons/dollar'; -import WalletIcon from 'app/modules/ui/icons/wallet'; +import DollarIcon from 'app/src/ui/icons/dollar'; +import WalletIcon from 'app/src/ui/icons/wallet'; import { A } from '@expo/html-elements'; -import { stellarAccountLink } from 'app/functions/utils'; +import { stellarAccountLink } from 'app/src/functions/utils'; +import { PaymentsStore } from '../stores/payments'; +import { userAtom } from '../atoms/atoms'; +import { useRecoilValue } from 'recoil'; -export default observer((props) => { - const { paymentsStore, sessionStore } = Stores(); +export default (props) => { + const { credits, loadingBalance } = PaymentsStore(); + const user = useRecoilValue(userAtom); const renderDollarSign = () => { - if (paymentsStore.credits > 0) { + if (credits > 0) { return ( @@ -26,20 +28,18 @@ export default observer((props) => { }; const renderCreditsSection = () => { - if (paymentsStore.loadingBalance) { + if (loadingBalance) { return {' '}Loading Balance...; } return ( <> {renderDollarSign()} - - {paymentsStore.credits ? paymentsStore.credits.toFixed(4) : ''} - + {credits ? credits.toFixed(4) : ''} ); }; - if (!sessionStore.user) { + if (!user) { return null; } return ( @@ -47,19 +47,17 @@ export default observer((props) => { - {UserAvatarMedium(sessionStore.user)} + {UserAvatarMedium(user)} - - {sessionStore.user?.displayName} - + {user?.displayName} {renderCreditsSection()} - {sessionStore.user.publicKey ? ( + {user.publicKey ? ( { > - - {sessionStore.user.publicKey} - + {user.publicKey} ) : null} @@ -83,7 +79,7 @@ export default observer((props) => { ); -}); +}; const styles = StyleSheet.create({ container: { diff --git a/modules/profile/ProfileTopContainer.tsx b/src/profile/ProfileTopContainer.tsx similarity index 58% rename from modules/profile/ProfileTopContainer.tsx rename to src/profile/ProfileTopContainer.tsx index 73109fe..af4ff99 100644 --- a/modules/profile/ProfileTopContainer.tsx +++ b/src/profile/ProfileTopContainer.tsx @@ -1,40 +1,30 @@ import React from 'react'; import { View, StyleSheet, Text } from 'react-native'; -import Colors from 'app/constants/Colors'; -import { UserAvatarMedium } from 'app/modules/ui/UserAvatar'; -import { observer } from 'mobx-react'; -import { Stores } from 'app/functions/Stores'; +import Colors from 'app/src/constants/Colors'; +import { UserAvatarMedium } from 'app/src/ui/UserAvatar'; +import ProfileStore from '../stores/profile'; -export default observer((props) => { - const { profileStore } = Stores(); +export default (props) => { + const { user } = ProfileStore(); - const renderBlurSection = () => { - return ( + if (!user) { + return null; + } + return ( + - {UserAvatarMedium(profileStore.user)} + {UserAvatarMedium(user)} - {profileStore.user.displayName} + {user.displayName} - ); - }; - - if (!profileStore.user) { - return null; - } - let source; - if (profileStore.user.avatarUrl) { - source = { uri: profileStore.user.avatarUrl }; - } - if (source) { - return {renderBlurSection()}; - } - return {renderBlurSection()}; -}); + + ); +}; const styles = StyleSheet.create({ container: { diff --git a/modules/profile/WithdrawalModal.tsx b/src/profile/WithdrawalModal.tsx similarity index 86% rename from modules/profile/WithdrawalModal.tsx rename to src/profile/WithdrawalModal.tsx index 168e97b..e1dfe8a 100644 --- a/modules/profile/WithdrawalModal.tsx +++ b/src/profile/WithdrawalModal.tsx @@ -7,18 +7,21 @@ import { TextInput, Platform, } from 'react-native'; -import { observer } from 'mobx-react'; -import Colors from 'app/constants/Colors'; -import LargeBtn from 'app/modules/ui/LargeBtn'; -import { Stores } from 'app/functions/Stores'; +import Colors from 'app/src/constants/Colors'; +import LargeBtn from 'app/src/ui/LargeBtn'; import { useNavigation } from '@react-navigation/native'; -import cursorPointer from 'app/constants/CursorPointer'; -import CloseIcon from 'app/modules/ui/icons/x'; -import WalletIcon from 'app/modules/ui/icons/wallet'; -import DollarIcon from 'app/modules/ui/icons/dollar'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import CloseIcon from 'app/src/ui/icons/x'; +import WalletIcon from 'app/src/ui/icons/wallet'; +import DollarIcon from 'app/src/ui/icons/dollar'; +import { PaymentsStore } from '../stores/payments'; -export default observer((props) => { - const { paymentsStore } = Stores(); +export default (props) => { + const { + credits, + withdrawToExternalWallet, + submittingWithdraw, + } = PaymentsStore(); const { goBack } = useNavigation(); const [address, setAddress] = useState(''); const [creditsToWithdraw, setCreditsToWithdraw] = useState(0); @@ -29,7 +32,7 @@ export default observer((props) => { const updateAmount = ({ target }: any) => { let creditsToWithdraw = parseFloat(target.value); - if (paymentsStore.credits < creditsToWithdraw) { + if (credits < creditsToWithdraw) { return; } @@ -37,7 +40,7 @@ export default observer((props) => { }; const onWithdraw = async () => { - await paymentsStore.withdrawToExternalWallet(address, creditsToWithdraw); + await withdrawToExternalWallet(address, creditsToWithdraw); goBack(); }; @@ -54,9 +57,7 @@ export default observer((props) => { Withdraw credits - - Current balance: {paymentsStore.credits} - + Current balance: {credits} @@ -114,16 +115,12 @@ export default observer((props) => { ); -}); +}; const styles = StyleSheet.create({ fieldWithoutBorderTop: { diff --git a/modules/search/ChartsView.tsx b/src/search/ChartsView.tsx similarity index 65% rename from modules/search/ChartsView.tsx rename to src/search/ChartsView.tsx index b826068..40cce5f 100644 --- a/modules/search/ChartsView.tsx +++ b/src/search/ChartsView.tsx @@ -1,26 +1,30 @@ import React, { useEffect, useState } from 'react'; import { SafeAreaView, StyleSheet, FlatList, Text } from 'react-native'; -import BottomPlaceholder from 'app/modules/ui/BottomPlaceholder'; -import Colors from 'app/constants/Colors'; +import BottomPlaceholder from 'app/src/ui/BottomPlaceholder'; +import Colors from 'app/src/constants/Colors'; import SearchingLoader from '../ui/SearchingLoader'; import EntryChartRow from '../ui/EntryChartRow'; import ResponsiveLayout from '../ui/ResponsiveLayout'; -import { Stores } from 'app/functions/Stores'; -import { observer } from 'mobx-react'; +import { SearchStore } from '../stores/search'; +import { PlayerStore } from '../stores/player'; -export default observer((props) => { - const { playerStore, entriesSearchStore } = Stores(); +export default (props) => { + const { loadAndPlay, setPlaylistModeFromArray } = PlayerStore(); + const { + topChart, + hasMoreTopChart, + loadMoreTopChart, + loadingTopChart, + } = SearchStore(); const [page, setPage] = useState(0); const renderItem = (item, index) => { return ( playerStore.loadAndPlay(item)} + play={() => loadAndPlay(item)} entry={item} options={null} - disablePlaylistMode={() => - playerStore.setPlaylistModeFromArray(entriesSearchStore.topChart) - } + disablePlaylistMode={() => setPlaylistModeFromArray(topChart)} previousScreen={null} position={index + 1} /> @@ -32,17 +36,17 @@ export default observer((props) => { }, []); const handleLoadMore = () => { - if (!entriesSearchStore.hasMoreTopChart) return; + if (!hasMoreTopChart) return; setPage((oldPage) => oldPage + 1); - entriesSearchStore.loadMoreTopChart(page); + loadMoreTopChart(page); }; return ( - {entriesSearchStore.topChart.length > 0 && ( + {topChart.length > 0 && ( renderItem(item, index)} keyExtractor={(entry) => entry.id} onEndReached={handleLoadMore} @@ -50,15 +54,15 @@ export default observer((props) => { ListHeaderComponent={() => ( Top Beats )} - refreshing={entriesSearchStore.loadingTopChart} + refreshing={loadingTopChart} /> )} - {SearchingLoader(entriesSearchStore.loadingTopChart)} + {SearchingLoader(loadingTopChart)} ); -}); +}; const styles = StyleSheet.create({ container: { diff --git a/modules/search/EntryOptionsModal.tsx b/src/search/EntryOptionsModal.tsx similarity index 82% rename from modules/search/EntryOptionsModal.tsx rename to src/search/EntryOptionsModal.tsx index d209ab6..d66eb19 100644 --- a/modules/search/EntryOptionsModal.tsx +++ b/src/search/EntryOptionsModal.tsx @@ -1,21 +1,20 @@ import React from 'react'; import { View, StyleSheet, Image, Text, Pressable } from 'react-native'; -import { observer } from 'mobx-react'; -import Colors from 'app/constants/Colors'; -import LikeOptionRow from 'app/modules/search/LikeOptionRow'; -import SetPrice from 'app/modules/search/SetPrice'; -import { Stores } from 'app/functions/Stores'; +import Colors from 'app/src/constants/Colors'; +import LikeOptionRow from 'app/src/search/LikeOptionRow'; +import SetPrice from 'app/src/search/SetPrice'; import { useNavigation } from '@react-navigation/native'; -import cursorPointer from 'app/constants/CursorPointer'; -import BuyBtn from 'app/modules/ui/buy-btn/BuyBtn'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import BuyBtn from 'app/src/ui/buy-btn/BuyBtn'; +import { SessionStore } from '../stores/session'; -export default observer(({ route }) => { - const { sessionStore } = Stores(); +export default ({ route }) => { + const { user } = SessionStore(); const { entry, previousScreen } = route.params; const { goBack } = useNavigation(); const renderSetPrice = (entry: any) => { - if (!sessionStore.user) return; + if (!user) return; if (previousScreen === 'MyMusicScreen') { return ; } @@ -47,7 +46,7 @@ export default observer(({ route }) => { ); -}); +}; const styles = StyleSheet.create({ container: { diff --git a/modules/search/LikeOptionRow.tsx b/src/search/LikeOptionRow.tsx similarity index 78% rename from modules/search/LikeOptionRow.tsx rename to src/search/LikeOptionRow.tsx index 5deee26..c524327 100644 --- a/modules/search/LikeOptionRow.tsx +++ b/src/search/LikeOptionRow.tsx @@ -1,22 +1,21 @@ import React from 'react'; import { StyleSheet, View, Text, Pressable } from 'react-native'; -import { observer } from 'mobx-react'; -import Colors from 'app/constants/Colors'; -import { Stores } from 'app/functions/Stores'; +import Colors from 'app/src/constants/Colors'; import { useNavigation } from '@react-navigation/native'; -import cursorPointer from 'app/constants/CursorPointer'; -import LikeIcon from 'app/modules/ui/icons/like'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import LikeIcon from 'app/src/ui/icons/like'; +import { LikesStore } from '../stores/likes'; -export default observer(({ entry, iconOnly = false, size = 24 }) => { - const { likesStore } = Stores(); +export default ({ entry, iconOnly = false, size = 24 }) => { + const { toggleLike, isEntryLiked } = LikesStore(); const { goBack } = useNavigation(); const handleToggle = async () => { - await likesStore.toggleLike(entry); + await toggleLike(entry); goBack(); }; const isLiked = () => { - return likesStore.isEntryLiked(entry); + return isEntryLiked(entry); }; if (!entry) { return null; @@ -46,7 +45,7 @@ export default observer(({ entry, iconOnly = false, size = 24 }) => {
); -}); +}; var styles = StyleSheet.create({ field: { diff --git a/modules/search/PricingOptionsModal.tsx b/src/search/PricingOptionsModal.tsx similarity index 80% rename from modules/search/PricingOptionsModal.tsx rename to src/search/PricingOptionsModal.tsx index c27b7ae..cb2e3ed 100644 --- a/modules/search/PricingOptionsModal.tsx +++ b/src/search/PricingOptionsModal.tsx @@ -8,37 +8,44 @@ import { Switch, Platform, } from 'react-native'; -import { observer } from 'mobx-react'; -import Colors from 'app/constants/Colors'; -import cursorPointer from 'app/constants/CursorPointer'; -import { Stores } from 'app/functions/Stores'; +import Colors from 'app/src/constants/Colors'; +import cursorPointer from 'app/src/constants/CursorPointer'; import { useNavigation } from '@react-navigation/native'; import Slider from '@react-native-community/slider'; -import DollarIcon from 'app/modules/ui/icons/dollar'; -import PieIcon from 'app/modules/ui/icons/pie'; -import CircleIcon from 'app/modules/ui/icons/circle'; +import DollarIcon from 'app/src/ui/icons/dollar'; +import PieIcon from 'app/src/ui/icons/pie'; +import CircleIcon from 'app/src/ui/icons/circle'; +import { UserEntriesStore } from '../stores/user-entries'; +import { EntryStore } from '../stores/entry'; const SwitchWeb: any = Switch; -export default observer(({ route }) => { - const { entryStore, userEntriesStore } = Stores(); +export default ({ route }) => { + const { + equityForSale, + setAvailableForSale, + setPrice, + setEquityForSale, + equityForSalePercentage, + availableForSale, + price, + } = EntryStore(); + const { refreshEntries } = UserEntriesStore(); const { goBack } = useNavigation(); const { entry } = route.params; - let equityForSaleValue = entryStore.equityForSale - ? entryStore.equityForSale - : 0; + let equityForSaleValue = equityForSale ? equityForSale : 0; useEffect(() => { if (!entry) return; - entryStore.updateAvailableForSale(entry.forSale); + setAvailableForSale(entry.forSale); if (entry.price) { - entryStore.updatePrice(entry.price); + setPrice(entry.price); } }, []); const handleUpdatePricing = async (entry: any) => { - await entryStore.updatePricing(entry); - userEntriesStore.refreshEntries(); + await setPrice(entry); + refreshEntries(); goBack(); }; @@ -58,9 +65,7 @@ export default observer(({ route }) => { { {'Available for Sale: '} - entryStore.updateAvailableForSale(forSale) - } - value={entryStore.availableForSale} + onValueChange={(forSale) => setAvailableForSale(forSale)} + value={availableForSale} style={styles.switch} activeThumbColor={Colors.defaultTextLight} trackColor={{ @@ -105,8 +108,8 @@ export default observer(({ route }) => { Platform.OS === 'web' ? ({ outlineWidth: 0 } as any) : {}, ]} placeholderTextColor="white" - value={entryStore.price ? String(entryStore.price) : undefined} - onChangeText={(price) => entryStore.updatePrice(parseInt(price))} + value={price ? String(price) : undefined} + onChangeText={(price) => setPrice(parseInt(price))} maxLength={30} />
@@ -118,7 +121,7 @@ export default observer(({ route }) => { numberOfLines={1} > {'Equity for Sale: '} - {entryStore.equityForSalePercentage} + {equityForSalePercentage} { maximumValue={100} value={equityForSaleValue} onValueChange={(target) => { - entryStore.updateEquityForSalePercentage(target); + setEquityForSale(target); }} step={1} minimumTrackTintColor={Colors.brandBlue} @@ -146,7 +149,7 @@ export default observer(({ route }) => {
); -}); +}; const styles = StyleSheet.create({ container: { diff --git a/modules/search/RecentlyAdded.tsx b/src/search/RecentlyAdded.tsx similarity index 61% rename from modules/search/RecentlyAdded.tsx rename to src/search/RecentlyAdded.tsx index d9a119c..e2124d8 100644 --- a/modules/search/RecentlyAdded.tsx +++ b/src/search/RecentlyAdded.tsx @@ -1,27 +1,31 @@ import React, { useState } from 'react'; import { Text, StyleSheet, SafeAreaView, FlatList } from 'react-native'; -import { observer } from 'mobx-react'; -import EntryRow from 'app/modules/ui/EntryRow'; -import SearchingLoader from 'app/modules/ui/SearchingLoader'; -import Colors from 'app/constants/Colors'; -import { Stores } from 'app/functions/Stores'; +import EntryRow from 'app/src/ui/EntryRow'; +import SearchingLoader from 'app/src/ui/SearchingLoader'; +import Colors from 'app/src/constants/Colors'; import ResponsiveLayout from '../ui/ResponsiveLayout'; import BottomPlaceholder from '../ui/BottomPlaceholder'; +import { SearchStore } from '../stores/search'; +import { PlayerStore } from '../stores/player'; -export default observer((props) => { - const { playerStore, entriesSearchStore } = Stores(); +export default (props) => { + const { loadAndPlay, setPlaylistModeFromArray } = PlayerStore(); + const { + recentlyAdded, + hasMoreRecentlyAdded, + loadMoreRecentlyAdded, + loadingRecentlyAdded, + } = SearchStore(); const [page, setPage] = useState(1); const renderItem = (item) => { return ( playerStore.loadAndPlay(item)} + play={() => loadAndPlay(item)} entry={item} options={null} disablePlaylistMode={() => { - playerStore.setPlaylistModeFromArray( - entriesSearchStore.recentlyAdded - ); + setPlaylistModeFromArray(recentlyAdded); }} previousScreen={null} /> @@ -29,17 +33,17 @@ export default observer((props) => { }; const handleLoadMore = () => { - if (!entriesSearchStore.hasMoreRecentlyAdded) return; + if (!hasMoreRecentlyAdded) return; setPage((oldPage) => oldPage + 1); - entriesSearchStore.loadMoreRecentlyAdded(page); + loadMoreRecentlyAdded(page); }; return ( - {entriesSearchStore.recentlyAdded.length > 0 && ( + {recentlyAdded.length > 0 && ( renderItem(item)} keyExtractor={(entry) => entry.id} onEndReached={handleLoadMore} @@ -47,15 +51,15 @@ export default observer((props) => { ListHeaderComponent={() => ( Recently Added )} - refreshing={entriesSearchStore.loadingRecentlyAdded} + refreshing={loadingRecentlyAdded} /> )} - {SearchingLoader(entriesSearchStore.loadingRecentlyAdded)} + {SearchingLoader(loadingRecentlyAdded)} ); -}); +}; const styles = StyleSheet.create({ recentText: { diff --git a/modules/search/RemoveFromMyMusicRow.tsx b/src/search/RemoveFromMyMusicRow.tsx similarity index 69% rename from modules/search/RemoveFromMyMusicRow.tsx rename to src/search/RemoveFromMyMusicRow.tsx index 68d0c4e..6b714f0 100644 --- a/modules/search/RemoveFromMyMusicRow.tsx +++ b/src/search/RemoveFromMyMusicRow.tsx @@ -1,23 +1,22 @@ import React from 'react'; import { StyleSheet, Pressable, View, Text } from 'react-native'; -import { observer } from 'mobx-react'; -import RemoveIcon from 'app/modules/ui/icons/remove'; -import Colors from 'app/constants/Colors'; -import * as stores from 'app/skyhitz-common'; -import { Stores } from 'app/functions/Stores'; +import RemoveIcon from 'app/src/ui/icons/remove'; +import Colors from 'app/src/constants/Colors'; import { useNavigation } from '@react-navigation/native'; +import { UserEntriesStore } from '../stores/user-entries'; +import { EntryStore } from '../stores/entry'; -type Stores = typeof stores; - -export default observer(({ entry }) => { - const { userEntriesStore, entryStore } = Stores(); +export default ({ entry }) => { + const { remove } = EntryStore(); + const { refreshEntries } = UserEntriesStore(); const { goBack } = useNavigation(); const handleRemoveEntry = async () => { - await entryStore.remove(entry.id); - await userEntriesStore.refreshEntries(); + await remove(entry.id); + await refreshEntries(); goBack(); }; + if (!entry) { return null; } @@ -29,7 +28,7 @@ export default observer(({ entry }) => { ); -}); +}; var styles = StyleSheet.create({ field: { diff --git a/src/search/SearchEntryList.tsx b/src/search/SearchEntryList.tsx new file mode 100644 index 0000000..0e0d76f --- /dev/null +++ b/src/search/SearchEntryList.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { ScrollView } from 'react-native'; +import EntryRow from 'app/src/ui/EntryRow'; +import SearchingLoader from 'app/src/ui/SearchingLoader'; +import Colors from 'app/src/constants/Colors'; +import BottomPlaceholder from 'app/src/ui/BottomPlaceholder'; +import ResponsiveLayout from '../ui/ResponsiveLayout'; +import { SearchStore } from '../stores/search'; + +const SearchEntryList = () => { + const { searching, entries } = SearchStore(); + const query = ''; + const loadPlayAndPushToCueList = () => {}; + const disablePlaylistMode = () => {}; + + return ( + + + {SearchingLoader(searching, query)} + {entries.map((entry: any) => ( + + ))} + + + + ); +}; + +export default SearchEntryList; diff --git a/src/search/SearchEntryView.tsx b/src/search/SearchEntryView.tsx new file mode 100644 index 0000000..d227887 --- /dev/null +++ b/src/search/SearchEntryView.tsx @@ -0,0 +1,30 @@ +import React, { useCallback, useEffect } from 'react'; +import { useRecoilValue } from 'recoil'; +import SearchEntryList from 'app/src/search/SearchEntryList'; +import RecentlyAdded from 'app/src/search/RecentlyAdded'; +import { useFocusEffect } from '@react-navigation/native'; +import { SearchStore, activeSearchAtom } from '../stores/search'; + +export default () => { + const { getRecentlyAdded, updateSearchType } = SearchStore(); + const active = useRecoilValue(activeSearchAtom); + + useFocusEffect( + useCallback(() => { + updateSearchType('entries'); + }, []) + ); + + const handleRecentlyAdded = async () => { + await getRecentlyAdded(); + }; + + useEffect(() => { + handleRecentlyAdded(); + }, []); + + if (active) { + return ; + } + return ; +}; diff --git a/src/search/SearchHeader.tsx b/src/search/SearchHeader.tsx new file mode 100644 index 0000000..f99242c --- /dev/null +++ b/src/search/SearchHeader.tsx @@ -0,0 +1,44 @@ +import React, { useState } from 'react'; +import { Platform } from 'react-native'; +import Colors from 'app/src/constants/Colors'; +import SearchBar from 'app/src/ui/searchbar/SearchBar'; +import { SearchStore } from '../stores/search'; + +let platform = Platform.OS === 'ios' ? 'ios' : 'android'; + +const SearchHeader = () => { + const [value, setValue] = useState(''); + const { search } = SearchStore(); + + const changeText = (value: any) => { + setValue(value); + }; + + return ( + { + search(q); + changeText(q); + }} + placeholder="Search" + icon={{ + style: { top: 15 }, + color: Colors.searchTextColor, + name: 'search', + }} + clearIcon={true} + value={value} + autoCorrect={false} + placeholderTextColor={Colors.searchTextColor} + inputStyle={{ + height: 30, + margin: 7.5, + borderRadius: 5, + color: Colors.searchTextColor, + }} + /> + ); +}; + +export default SearchHeader; diff --git a/modules/search/SearchNavigator.tsx b/src/search/SearchNavigator.tsx similarity index 81% rename from modules/search/SearchNavigator.tsx rename to src/search/SearchNavigator.tsx index 17f7da1..b0824a3 100644 --- a/modules/search/SearchNavigator.tsx +++ b/src/search/SearchNavigator.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { createStackNavigator } from '@react-navigation/stack'; -import SearchTabsView from 'app/modules/search/SearchTabsView'; -import Colors from 'app/constants/Colors'; -import ProfileScreen from 'app/modules/profile/ProfileScreen'; +import SearchTabsView from 'app/src/search/SearchTabsView'; +import Colors from 'app/src/constants/Colors'; +import ProfileScreen from 'app/src/profile/ProfileScreen'; const Stack = createStackNavigator(); diff --git a/modules/search/SearchTabsView.tsx b/src/search/SearchTabsView.tsx similarity index 91% rename from modules/search/SearchTabsView.tsx rename to src/search/SearchTabsView.tsx index c38ee95..2492802 100644 --- a/modules/search/SearchTabsView.tsx +++ b/src/search/SearchTabsView.tsx @@ -4,9 +4,9 @@ import { createMaterialTopTabNavigator, MaterialTopTabBar, } from '@react-navigation/material-top-tabs'; -import SearchEntryView from 'app/modules/search/SearchEntryView'; -import SearchUserView from 'app/modules/search/SearchUserView'; -import Colors from 'app/constants/Colors'; +import SearchEntryView from 'app/src/search/SearchEntryView'; +import SearchUserView from 'app/src/search/SearchUserView'; +import Colors from 'app/src/constants/Colors'; import SearchHeader from './SearchHeader'; import ResponsiveLayout from '../ui/ResponsiveLayout'; diff --git a/src/search/SearchUserList.tsx b/src/search/SearchUserList.tsx new file mode 100644 index 0000000..9fcf1d4 --- /dev/null +++ b/src/search/SearchUserList.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { ScrollView } from 'react-native'; +import UserRow from 'app/src/ui/UserRow'; +import SearchingLoader from 'app/src/ui/SearchingLoader'; +import Colors from 'app/src/constants/Colors'; +import BottomPlaceholder from 'app/src/ui/BottomPlaceholder'; +import ResponsiveLayout from '../ui/ResponsiveLayout'; +import { SearchStore } from '../stores/search'; + +const UserSearchList = () => { + const { users, searching, query } = SearchStore(); + + return ( + + + {SearchingLoader(searching, query)} + {users.map((user) => ( + + ))} + + + + ); +}; + +export default UserSearchList; diff --git a/src/search/SearchUserView.tsx b/src/search/SearchUserView.tsx new file mode 100644 index 0000000..699dde0 --- /dev/null +++ b/src/search/SearchUserView.tsx @@ -0,0 +1,19 @@ +import React, { useCallback } from 'react'; +import SearchUserList from 'app/src/search/SearchUserList'; +import { useFocusEffect } from '@react-navigation/native'; +import { SearchStore } from '../stores/search'; + +export default () => { + const { updateSearchType, active } = SearchStore(); + + useFocusEffect( + useCallback(() => { + updateSearchType('users'); + }, []) + ); + + if (active) { + return ; + } + return null; +}; diff --git a/modules/search/SetPrice.tsx b/src/search/SetPrice.tsx similarity index 88% rename from modules/search/SetPrice.tsx rename to src/search/SetPrice.tsx index b2cfc03..00b29e2 100644 --- a/modules/search/SetPrice.tsx +++ b/src/search/SetPrice.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { StyleSheet, Pressable, View, Text } from 'react-native'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; import { CommonActions, useNavigation } from '@react-navigation/native'; -import cursorPointer from 'app/constants/CursorPointer'; -import DollarIcon from 'app/modules/ui/icons/dollar'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import DollarIcon from 'app/src/ui/icons/dollar'; export default ({ entry }) => { const { dispatch, goBack } = useNavigation(); diff --git a/stellar/index.ts b/src/stellar/index.ts similarity index 97% rename from stellar/index.ts rename to src/stellar/index.ts index 3282b35..ee7a771 100644 --- a/stellar/index.ts +++ b/src/stellar/index.ts @@ -1,4 +1,4 @@ -import { Config } from 'app/skyhitz-common/src/config'; +import { Config } from 'app/src/config'; import { TransactionBuilder, Account, diff --git a/src/stores/entry.ts b/src/stores/entry.ts new file mode 100644 index 0000000..c8d6455 --- /dev/null +++ b/src/stores/entry.ts @@ -0,0 +1,318 @@ +import { atom, selector, useRecoilState, useRecoilValue } from 'recoil'; +import { nftStorageApi } from '../config/constants'; +import { entriesBackend } from '../api/entries'; + +const uploadingVideoAtom = atom({ + key: 'uploadingVideo', + default: false, +}); +const uploadingErrorAtom = atom({ + key: 'uploadingError', + default: '', +}); +const loadingVideoAtom = atom({ + key: 'loadingVideo', + default: false, +}); +const loadingImageAtom = atom({ + key: 'loadingImage', + default: false, +}); +const descriptionAtom = atom({ + key: 'description', + default: '', +}); +const issuerAtom = atom({ + key: 'issuer', + default: '', +}); +const titleAtom = atom({ + key: 'title', + default: '', +}); +const artistAtom = atom({ + key: 'artist', + default: '', +}); +const availableForSaleAtom = atom({ + key: 'availableForSale', + default: false, +}); +const equityForSaleAtom = atom({ + key: 'equityForSale', + default: 1, +}); +const equityForSalePercentageAtom = selector({ + key: 'equityForSalePercentage', + get: ({ get }) => { + const equity = get(equityForSaleAtom); + return `${equity} %`; + }, +}); +const priceAtom = atom({ + key: 'price', + default: 1, +}); +const filesProgressAtom = atom<{ [key: string]: number }>({ + key: 'filesProgress', + default: {}, +}); + +const currentUploadAtom = selector({ + key: 'currentUpload', + get: () => { + return Object.keys(filesProgressAtom).includes('video') + ? 'video' + : Object.keys(filesProgressAtom).includes('meta') + ? 'meta' + : 'none'; + }, +}); +const canCreateAtom = selector({ + key: 'canCreate', + get: ({ get }) => { + return ( + get(imageBlobAtom) && + get(videoBlobAtom) && + get(descriptionAtom) && + get(titleAtom) && + get(artistAtom) + ); + }, +}); +const progressAtom = selector({ + key: 'progress', + get: () => { + return Math.min(...Object.values(filesProgressAtom)); + }, +}); +const creatingAtom = atom({ + key: 'creating', + default: false, +}); +const imageBlobAtom = atom({ + key: 'imageBlob', + default: undefined, +}); +const videoBlobAtom = atom({ + key: 'videoBlob', + default: undefined, +}); + +export const EntryStore = () => { + const [filesProgress, setFilesProgress] = useRecoilState(filesProgressAtom); + const [issuer, setIssuer] = useRecoilState(issuerAtom); + const [uploadingError, setUploadingError] = useRecoilState( + uploadingErrorAtom + ); + const [creating, setCreating] = useRecoilState(creatingAtom); + const [loadingVideo, setLoadingVideo] = useRecoilState(loadingVideoAtom); + const [loadingImage, setLoadingImage] = useRecoilState(loadingImageAtom); + const [uploadingVideo, setUploadingVideo] = useRecoilState( + uploadingVideoAtom + ); + const [description, setDescription] = useRecoilState(descriptionAtom); + const [title, setTitle] = useRecoilState(titleAtom); + const [artist, setArtist] = useRecoilState(artistAtom); + const [availableForSale, setAvailableForSale] = useRecoilState( + availableForSaleAtom + ); + const [price, setPrice] = useRecoilState(priceAtom); + const [equityForSale, setEquityForSale] = useRecoilState(equityForSaleAtom); + const [imageBlob, setImageBlob] = useRecoilState(imageBlobAtom); + const [videoBlob, setVideoBlob] = useRecoilState(videoBlobAtom); + const equityForSalePercentage = useRecoilValue(equityForSalePercentageAtom); + const currentUpload = useRecoilValue(currentUploadAtom); + const canCreate = useRecoilValue(canCreateAtom); + const progress = useRecoilValue(progressAtom); + + const clearUploadingError = () => { + setUploadingError(''); + }; + + const uploadFile = async (file: any, id: string) => { + return new Promise((resolve: (value: string) => void, reject) => { + setFilesProgress((oldValue) => { + oldValue[id] = 0; + return oldValue; + }); + let xhr = new XMLHttpRequest(); + xhr.open('POST', `${nftStorageApi}/upload`, true); + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + xhr.setRequestHeader( + 'Authorization', + `Bearer ${process.env.NEXT_PUBLIC_NFT_STORAGE_API_KEY}` + ); + + xhr.upload.addEventListener('progress', (e) => { + const progress = Math.round((e.loaded * 100.0) / e.total); + + setFilesProgress((oldValue) => { + oldValue[id] = progress; + return oldValue; + }); + if (progress === 100) { + setFilesProgress((oldValue) => { + delete oldValue[id]; + return oldValue; + }); + } + }); + + xhr.onreadystatechange = () => { + if (xhr.readyState == 4 && xhr.status == 200) { + let { value, ok } = JSON.parse(xhr.responseText); + if (!ok) { + setUploadingError('Something went wrong!'); + reject(); + return; + } + resolve(value.cid); + } + }; + + xhr.send(file); + }); + }; + + const storeNFT = async () => { + const name = `${artist} - ${title}`; + const ipfsProtocol = 'ipfs://'; + + const code = `${title}${artist}` + .normalize('NFD') + .replace(/\p{Diacritic}/gu, '') + .replace(/ /g, '') + .replace(/-/g, '') + .replace(/[^0-9a-z]/gi, '') + .substr(0, 12) + .toUpperCase(); + + const [imageCid, videoCid] = [ + await uploadFile(imageBlob, 'image'), + await uploadFile(videoBlob, 'video'), + ]; + + const imageUrl = `${ipfsProtocol}${imageCid}`; + const videoUrl = `${ipfsProtocol}${videoCid}`; + + const issuer = await entriesBackend.getIssuer(videoCid); + setIssuer(issuer); + if (!issuer) throw 'could not generate issuer'; + + const json = { + name: name, + description: description, + code: code, + issuer: issuer, + domain: 'skyhitz.io', + supply: 1, + image: imageUrl, + animation_url: videoUrl, + video: videoUrl, + url: videoUrl, + }; + + const blob = new Blob([JSON.stringify(json)], { type: 'application/json' }); + + const nftCid = await uploadFile(blob, 'meta'); + return { videoCid, nftCid, imageUrl, videoUrl, code }; + }; + + const clearStore = () => { + setUploadingVideo(false); + setLoadingImage(false); + setDescription(''); + setTitle(''); + setArtist(''); + setAvailableForSale(false); + setPrice(0); + setEquityForSale(1); + setCreating(false); + }; + + const indexEntry = async (issuerPayload = issuer) => { + if (!issuerPayload) return false; + return await entriesBackend.indexEntry(issuerPayload); + }; + + const create = async () => { + setCreating(true); + const { videoCid, nftCid, imageUrl, videoUrl, code } = await storeNFT(); + if (!nftCid || !imageUrl || !videoUrl) { + setUploadingError('Could not store NFT'); + return; + } + return await entriesBackend.createFromUpload( + videoCid, + nftCid, + code, + availableForSale, + price, + equityForSale + ); + }; + + const updatePricing = async (entry: any) => { + if (!availableForSale) { + return; + } + if (!price) { + return; + } + if (!equityForSale) { + return; + } + await entriesBackend.updatePricing( + entry.id, + price, + availableForSale, + equityForSale + ); + clearStore(); + }; + + const remove = async (entryId: string) => { + await entriesBackend.remove(entryId); + }; + + return { + remove, + updatePricing, + create, + indexEntry, + clearStore, + storeNFT, + uploadFile, + clearUploadingError, + filesProgress, + uploadingError, + creating, + loadingVideo, + loadingImage, + uploadingVideo, + setLoadingVideo, + setImageBlob, + setVideoBlob, + equityForSalePercentage, + currentUpload, + canCreate, + progress, + issuer, + equityForSale, + setUploadingError, + setArtist, + artist, + setDescription, + title, + setTitle, + availableForSale, + setAvailableForSale, + description, + price, + setPrice, + setEquityForSale, + imageBlob, + videoBlob, + }; +}; diff --git a/src/stores/likes.ts b/src/stores/likes.ts new file mode 100644 index 0000000..a07ca34 --- /dev/null +++ b/src/stores/likes.ts @@ -0,0 +1,171 @@ +import { useRecoilValue } from 'recoil'; +import { likesBackend } from '../api/likes'; +import { userAtom } from '../atoms/atoms'; +import { Entry, User } from '../models'; + +export const LikesStore = () => { + let ids: Set = new Set([]); + let loading: boolean = false; + let loadingEntryLikes: boolean = false; + let entry!: Entry; + let entryLikes: User[] = []; + let entryLikesCount!: number; + let userLikes: Entry[] = []; + let userLikesCount!: number; + + const user = useRecoilValue(userAtom); + + let viewLimit: number = 8; + + const hasMoreLikers = () => { + if (entryLikesCount > viewLimit) { + return true; + } + return false; + }; + + const plusLikers = () => { + return kFormatter(entryLikesCount - viewLimit); + }; + + const kFormatter = (num: number) => { + return num > 999 ? (num / 1000).toFixed(1) + 'k' : num; + }; + + // TODO: wire up effects + // constructor( + // public observables: IObservableObject, + // public session: IObservableObject + // ) { + // this.disposer = observe(observables, ({ object }) => { + // if (!object.entry) { + // return; + // } + // this.entry = object.entry; + // this.refreshEntryLikes(this.entry.id); + // }); + + // this.userDisposer = observe(this.session, ({ object }) => { + // this.user = object.user; + // }); + // } + + const clearLikes = () => { + entryLikes = []; + userLikes = []; + }; + + const refreshEntryLikes = (id: string) => { + loadingEntryLikes = true; + likesBackend.entryLikes(id).then((payload) => { + if (payload) { + entryLikesCount = payload.count; + let users = payload.users.map( + (userPayload: any) => new User(userPayload) + ); + entryLikes = users; + } + + loadingEntryLikes = false; + }); + }; + + const refreshLikes = () => { + loading = true; + likesBackend.userLikes().then((userLikes) => { + if (!userLikes) { + return; + } else { + let ids = userLikes.map((like: any) => like.id); + let entries = userLikes.map((like: any) => new Entry(like)); + ids = new Set(ids); + userLikes = entries; + userLikesCount = userLikes.length; + } + + loading = false; + }); + }; + + const unlike = async (entry: Entry) => { + ids.delete(entry.id); + let index = userLikes.findIndex((like) => { + if (like) { + return like.id === entry.id; + } + return false; + }); + userLikes.splice(index, 1); + let unliked = await likesBackend.like(entry.id, false); + if (!unliked) { + ids = ids.add(entry.id); + userLikes.push(entry); + } + userLikesCount = userLikes.length; + + let userIndex = entryLikes.findIndex((like) => { + if (like) { + return like.id === user?.id; + } + return false; + }); + entryLikes.splice(userIndex, 1); + }; + + const like = async (entry: Entry) => { + if (!user) return; + ids = ids.add(entry.id); + userLikes.push(entry); + let liked = await likesBackend.like(entry.id); + if (!liked) { + ids.delete(entry.id); + let index = userLikes.findIndex((like) => { + if (like) { + return like.id === entry.id; + } + return false; + }); + userLikes.splice(index, 1); + } + userLikesCount = userLikes.length; + + entryLikes.push(user); + }; + + const toggleLike = (entry: Entry) => { + if (isEntryLiked(entry)) { + return unlike(entry); + } + return like(entry); + }; + + const isLiked = () => { + if (!entry) { + return false; + } + return ids.has(entry.id); + }; + + const isEntryLiked = (entry: Entry) => { + if (!entry) { + return false; + } + return ids.has(entry.id); + }; + + return { + toggleLike, + isLiked, + entryLikes, + hasMoreLikers, + plusLikers, + userLikes, + userLikesCount, + loading, + refreshLikes, + isEntryLiked, + clearLikes, + refreshEntryLikes, + loadingEntryLikes, + }; +}; diff --git a/src/stores/payments.ts b/src/stores/payments.ts new file mode 100644 index 0000000..214592d --- /dev/null +++ b/src/stores/payments.ts @@ -0,0 +1,162 @@ +import { paymentsBackend } from '../api/payments'; +import { entriesBackend } from '../api/entries'; +import { Config } from '../config'; +import { atom, useRecoilState } from 'recoil'; + +const subscribedAtom = atom({ + key: 'subscribed', + default: false, +}); +const subscriptionLoadedAtom = atom({ + key: 'subscriptionLoaded', + default: false, +}); +const creditsAtom = atom({ + key: 'credits', + default: 0, +}); +const submittingSubscriptionAtom = atom({ + key: 'submittingSubscription', + default: false, +}); +const submittingWithdrawAtom = atom({ + key: 'submittingWithdraw', + default: false, +}); +const loadingBalanceAtom = atom({ + key: 'loadingBalance', + default: false, +}); +const xlmPriceAtom = atom({ + key: 'xlmPrice', + default: 0, +}); +const entryPricesAtom = atom>({ + key: 'entryPrices', + default: new Map(), +}); + +export const PaymentsStore = () => { + const [submittingSubscription, setSubmittingSubscription] = useRecoilState( + submittingSubscriptionAtom + ); + const [subscribed, setSubscribed] = useRecoilState(subscribedAtom); + const [loadingBalance, setLoadingBalance] = useRecoilState( + loadingBalanceAtom + ); + const [credits, setCredits] = useRecoilState(creditsAtom); + const [subscriptionLoaded, setSubscriptionLoaded] = useRecoilState( + subscriptionLoadedAtom + ); + const [submittingWithdraw, setSubmittingWithdraw] = useRecoilState( + submittingWithdrawAtom + ); + const [xlmPrice, setXlmPrice] = useRecoilState(xlmPriceAtom); + const [entryPrices, setEntryPrices] = useRecoilState(entryPricesAtom); + + const subscribeUser = async (cardToken: string) => { + setSubmittingSubscription(true); + await paymentsBackend.subscribe(cardToken); + setSubmittingSubscription(false); + setSubscribed(true); + return true; + }; + + const buyCredits = async (cardToken: string, amount: number) => { + setSubmittingSubscription(true); + await paymentsBackend.buyCredits(cardToken, amount); + setSubmittingSubscription(false); + setSubscribed(true); + return true; + }; + + const refreshSubscription = async () => { + setLoadingBalance(true); + let { subscribed, credits } = await paymentsBackend.refreshSubscription(); + setSubscribed(subscribed); + setCredits(credits); + setSubscriptionLoaded(true); + setLoadingBalance(false); + }; + + const withdrawToExternalWallet = async ( + withdrawAddress: string, + creditsToWithdraw: number + ) => { + setSubmittingWithdraw(true); + await paymentsBackend.withdrawToExternalWallet( + withdrawAddress, + creditsToWithdraw + ); + await refreshSubscription(); + setSubmittingWithdraw(false); + }; + + const buyEntry = async (id: string, amount: number, price: number) => { + return await entriesBackend.buyEntry(id, amount, price); + }; + + const getPriceInfo = (id: string) => { + return entriesBackend.getPriceInfo(id); + }; + + const xlmPriceWithFees = () => { + return xlmPrice * 1.06; + }; + + const refreshXLMPrice = async () => { + const price = await paymentsBackend.getXLMPrice(); + + setXlmPrice(parseFloat(price)); + }; + + const fetchPriceFromHorizon = async (code: string, issuer: string) => { + let { asks } = await fetch( + `${Config.HORIZON_URL}/order_book?selling_asset_type=credit_alphanum12&selling_asset_code=${code}&selling_asset_issuer=${issuer}&buying_asset_type=native` + ).then((res: any) => res.json()); + + if (asks && asks[0]) { + let { price, amount }: { price: string; amount: string } = asks[0]; + return { price: parseFloat(price), amount: parseFloat(amount) }; + } + + return null; + }; + + const fetchAndCachePrice = async (code: string, issuer: string) => { + const identifier = `${code}-${issuer}`; + const val = entryPrices.get(identifier); + if (val) { + return val; + } + const newval = await fetchPriceFromHorizon(code, issuer); + if (newval) { + setEntryPrices((oldValue) => oldValue.set(identifier, newval)); + return newval; + } + return { price: 0, amount: 0 }; + }; + + return { + fetchAndCachePrice, + fetchPriceFromHorizon, + refreshXLMPrice, + xlmPriceWithFees, + getPriceInfo, + buyEntry, + withdrawToExternalWallet, + refreshSubscription, + buyCredits, + subscribeUser, + entryPrices, + xlmPrice, + submittingSubscription, + submittingWithdraw, + subscriptionLoaded, + credits, + loadingBalance, + subscribed, + setLoadingBalance, + setSubmittingSubscription, + }; +}; diff --git a/src/stores/player.ts b/src/stores/player.ts new file mode 100644 index 0000000..00b6495 --- /dev/null +++ b/src/stores/player.ts @@ -0,0 +1,621 @@ +import { Entry } from '../models'; +import { entriesBackend } from '../api/entries'; +import { PlaybackState, SeekState, ControlsState } from '../types/index'; +import { Platform } from 'react-native'; +import { atom, useRecoilState } from 'recoil'; + +const entryAtom = atom({ + key: 'playerEntry', + default: null, +}); +const showMiniPlayerAtom = atom({ + key: 'showMiniPlayer', + default: false, +}); +const showAtom = atom({ + key: 'show', + default: false, +}); +const tabBarBottomAtom = atom({ + key: 'tabBarBottom', + default: 0, +}); +const loopAtom = atom({ + key: 'loop', + default: false, +}); +const shuffleAtom = atom({ + key: 'shuffle', + default: false, +}); +const playbackStateAtom = atom({ + key: 'playbackState', + default: 'LOADING', +}); +const seekStateAtom = atom({ + key: 'seekState', + default: 'NOT_SEEKING', +}); +const controlsStateAtom = atom({ + key: 'controlsState', + default: 'SHOWN', +}); +const shouldPlayAtom = atom({ + key: 'shouldPlay', + default: false, +}); +const fullscreenAtom = atom({ + key: 'fullscreen', + default: false, +}); +const positionMillisAtom = atom({ + key: 'positionMillis', + default: 0, +}); + +const playbackInstancePositionAtom = atom({ + key: 'playbackInstancePosition', + default: 0, +}); +const playbackInstanceDurationAtom = atom({ + key: 'playbackInstanceDuration', + default: 0, +}); +const lastPlaybackStateUpdateAtom = atom({ + key: 'lastPlaybackStateUpdate', + default: Date.now(), +}); +const errorAtom = atom({ key: 'error' }); +const networkStateAtom = atom({ key: 'networkState' }); +const shouldPlayAtEndOfSeekAtom = atom({ + key: 'shouldPlayAtEndOfSeek', + default: false, +}); +const sliderWidthAtom = atom({ key: 'sliderWidth', default: 0 }); +const cueListAtom = atom({ key: 'cueList', default: [] }); +const currentIndexAtom = atom({ key: 'currentIndex', default: 0 }); +const playlistModeAtom = atom({ key: 'playlistMode', default: false }); +const playbackInstanceAtom = atom({ key: 'playbackInstance' }); +const seekPositionAtom = atom({ key: 'seekPosition', default: 0 }); +const slidingAtom = atom({ key: 'sliding', default: false }); +const streamUrlAtom = atom({ key: 'streamUrl', default: '' }); +const videoAtom = atom({ key: 'video' }); + +export const PlayerStore = () => { + const [entry, setEntry] = useRecoilState(entryAtom); + const [showMiniPlayer, setShowMiniPlayer] = useRecoilState( + showMiniPlayerAtom + ); + const [show, setShow] = useRecoilState(showAtom); + const [tabBarBottom, setTabBarBottom] = useRecoilState(tabBarBottomAtom); + const [loop, setLoop] = useRecoilState(loopAtom); + const [shuffle, setShuffle] = useRecoilState(shuffleAtom); + const [playbackState, setPlaybackState] = useRecoilState(playbackStateAtom); + const [seekState, setSeekState] = useRecoilState(seekStateAtom); + const [controlsState, setControlsState] = useRecoilState(controlsStateAtom); + const [shouldPlay, setShouldPlay] = useRecoilState(shouldPlayAtom); + const [fullscreen, setFullscreen] = useRecoilState(fullscreenAtom); + const [positionMillis, setPositionMillis] = useRecoilState( + positionMillisAtom + ); + const [ + playbackInstancePosition, + setPlaybackInstancePosition, + ] = useRecoilState(playbackInstancePositionAtom); + const [ + playbackInstanceDuration, + setPlaybackInstanceDuration, + ] = useRecoilState(playbackInstanceDurationAtom); + const [lastPlaybackState, setLastPlaybackState] = useRecoilState( + lastPlaybackStateUpdateAtom + ); + const [error, setError] = useRecoilState(errorAtom); + const [networkState, setNetworkState] = useRecoilState(networkStateAtom); + const [shouldPlayEndOfSeek, setShouldPlayEndofSeek] = useRecoilState( + shouldPlayAtEndOfSeekAtom + ); + const [sliderWidth, setSliderWidth] = useRecoilState(sliderWidthAtom); + const [cueList, setCueList] = useRecoilState(cueListAtom); + const [currentIndex, setCurrentIndex] = useRecoilState(currentIndexAtom); + const [playlistMode, setPlaylistMode] = useRecoilState(playlistModeAtom); + const [playbackInstance, setPlaybackInstance] = useRecoilState( + playbackInstanceAtom + ); + const [seekPosition, setSeekPosition] = useRecoilState(seekPositionAtom); + const [sliding, setSliding] = useRecoilState(slidingAtom); + const [streamUrl, setStreamUrl] = useRecoilState(streamUrlAtom); + const [video, setVideo] = useRecoilState(videoAtom); + + const mountVideo = (component) => { + if (!component) return; + + setVideo(component); + loadNewPlaybackInstance(false); + }; + + const loadNewPlaybackInstance = async ( + playing, + streamUrlPayload = streamUrl + ) => { + if (playbackInstance != null) { + try { + await playbackInstance.unloadAsync(); + } catch (e) {} + + setPlaybackInstance(null); + } + + if (!streamUrlPayload) return; + if (!video) return; + + await video.loadAsync( + { uri: streamUrlPayload }, + { + shouldPlay: playing, + positionMillis: 0, + progressUpdateIntervalMillis: 50, + } + ); + setPlaybackInstance(video); + if (playing && !isPlaying) { + playAsync(); + } + }; + + const refreshEntry = async () => { + if (entry && entry.id) { + let res = await entriesBackend.getById(entry.id); + setEntry(res); + } + }; + + const handleSetPlaylistMode = (entries: Entry[]) => { + setPlaylistMode(true); + setCueList(entries); + }; + + const setPlaylistModeFromArray = (entries: Entry[]) => { + setPlaylistMode(true); + setCueList(entries); + }; + + const disablePlaylistMode = () => { + setPlaylistMode(false); + setCueList([]); + }; + + const handleSetPlaybackInstance = (playbackInstance: any) => { + if (playbackInstance !== null) { + setPlaybackInstance(playbackInstance); + } + }; + + const playbackInstanceExists = () => { + return !!playbackInstance; + }; + + const playAsync = async () => { + if (playbackInstanceExists()) { + setPlaybackState('PLAYING'); + return await playbackInstance.setStatusAsync({ shouldPlay: true }); + } + }; + + const pauseAsync = async () => { + if (playbackInstanceExists()) { + setPlaybackState('PAUSED'); + await playbackInstance.setStatusAsync({ shouldPlay: false }); + setPlaybackState('PAUSED'); + return true; + } + }; + + const stopAsync = async () => { + if (playbackInstanceExists()) { + setPlaybackState('PAUSED'); + return await playbackInstance.setStatusAsync({ + shouldPlay: false, + positionMillis: 0, + }); + } + }; + + const toggleLoop = async () => { + if (playbackInstanceExists()) { + setLoop(!loop); + return await playbackInstance.setIsLoopingAsync(loop); + } + }; + + const presentFullscreenPlayer = async () => { + if (playbackInstance) { + await playbackInstance.presentFullscreenPlayer(); + return; + } + }; + + const dismissFullscreenPlayer = async () => { + if (playbackInstance) { + await playbackInstance.dismissFullscreenPlayer(); + return; + } + }; + + const onFullscreenUpdate = (status: any) => { + if (status.fullscreenUpdate === 1) { + setFullscreen(true); + } + + if (status.fullscreenUpdate === 3) { + setFullscreen(false); + // resume video manually, + // TODO: add bug to expo client on github. + if (shouldPlay) { + playAsync(); + } + } + }; + const isPlaying = () => { + if (playbackState === 'PLAYING') { + return true; + } + return false; + }; + + const togglePlay = async () => { + if (isPlaying()) { + return pauseAsync(); + } + return playAsync(); + }; + + const replay = async () => { + await stopAsync(); + setPlaybackState('PLAYING'); + return playAsync(); + }; + + const loadAndPlay = async (entry: Entry, play = true) => { + if (!entry) { + return null; + } + const currentIndex = cueList.findIndex( + (item) => !!item && item.id === entry.id + ); + if (currentIndex !== -1) { + setCurrentIndex(currentIndex); + } + + setPlaybackState('LOADING'); + setEntry(entry); + setShow(true); + let { videoUrl, isIpfs, videoSrc } = entry; + + if (!videoUrl) { + return; + } + + setStreamUrl(isIpfs && videoSrc ? videoSrc : videoUrl); + await loadNewPlaybackInstance(play, streamUrl); + setPlaybackState(play ? 'PLAYING' : 'PAUSED'); + return; + }; + + const playNext = async () => { + setPlaybackState('LOADING'); + pauseAsync(); + + if (isCurrentIndexAtTheEndOfCue()) { + // Override the value if playlistMode was set to true, it will loop through the + // list instead of playing a related video. + if (playlistMode) { + let entry = cueList[0]; + setCurrentIndex(0); + if (!entry) return; + return loadAndPlay(entry); + } + } + + setCurrentIndex((oldVal) => oldVal++); + let nextEntry = cueList[currentIndex]; + if (!nextEntry) return; + loadAndPlay(nextEntry); + }; + + const loadPlayAndPushToCueList = async (entry: Entry) => { + loadAndPlay(entry); + setCueList((oldVal) => { + const arr = oldVal; + arr.push(entry); + return arr; + }); + setCurrentIndex(cueList.length - 1); + }; + + const loadPlayAndUnshiftToCueList = async (entry: Entry) => { + loadAndPlay(entry); + setCueList((oldVal) => { + const arr = oldVal; + arr.unshift(entry); + return arr; + }); + setCurrentIndex(0); + }; + + const onError = (e: string) => { + console.info(e); + }; + + const toggleShuffle = () => { + setShuffle(!shuffle); + }; + + const unmountMiniPlayer = () => { + setShowMiniPlayer(false); + }; + + const mountMiniPlayer = () => { + setShowMiniPlayer(true); + }; + + const hidePlayer = () => { + setShow(false); + }; + + const isCurrentIndexAtTheStartOfCue = () => { + return currentIndex === 0; + }; + + const isCurrentIndexAtTheEndOfCue = () => { + return currentIndex === cueList.length - 1; + }; + + const updateTabBarBottomPosition = (bottom: number) => { + setTabBarBottom(bottom); + }; + + const playPrev = async () => { + setPlaybackState('LOADING'); + pauseAsync(); + if (isCurrentIndexAtTheStartOfCue()) { + // Override the value if playlistMode was set to true, it will loop through the + // list instead of playing a related video. + if (playlistMode) { + let lastIndexInCueList = cueList.length - 1; + let entry = cueList[lastIndexInCueList]; + setCurrentIndex(lastIndexInCueList); + if (!entry) return; + return loadAndPlay(entry); + } + + return; + } + + setCurrentIndex((oldVal) => oldVal--); + let prevEntry = cueList[currentIndex]; + if (!prevEntry) return; + loadAndPlay(prevEntry); + }; + + const padWithZero = (value: number) => { + const result = value.toString(); + if (value < 10) { + return '0' + result; + } + return result; + }; + + const getMMSSFromMillis = (millis: number) => { + const totalSeconds = millis / 1000; + const seconds = Math.floor(totalSeconds % 60); + const minutes = Math.floor(totalSeconds / 60); + return padWithZero(minutes) + ':' + padWithZero(seconds); + }; + + const durationDisplay = () => { + return getMMSSFromMillis(playbackInstanceDuration); + }; + + const positionDisplay = () => { + return getMMSSFromMillis(playbackInstancePosition); + }; + + const onSeekSliderValueChange = () => { + if ( + playbackInstance !== null && + seekState !== 'SEEKING' && + seekState !== 'SEEKED' + ) { + setShouldPlayEndofSeek(false); + setSeekState('SEEKING'); + + if (isPlaying()) { + pauseAsync(); + setShouldPlayEndofSeek(true); + } + } + }; + + const onSeekSliderSlidingComplete = async (value: number) => { + if (seekState !== 'SEEKED') { + setSeekState('SEEKED'); + let status; + try { + status = await playbackInstance.setStatusAsync({ + positionMillis: value * playbackInstanceDuration, + shouldPlay: shouldPlayEndOfSeek, + }); + + setSeekState('NOT_SEEKING'); + setPlaybackState(getPlaybackStateFromStatus(status)); + } catch (message) {} + } + }; + + const onSeekBarTap = (evt: any) => { + if (sliding) return; + if ( + !( + playbackState === 'LOADING' || + playbackState === 'ENDED' || + playbackState === 'ERROR' || + controlsState !== 'SHOWN' + ) + ) { + let xValue; + if (Platform.OS === 'web') { + xValue = evt.nativeEvent.clientX - evt.target.getBoundingClientRect().x; + } else { + xValue = evt.nativeEvent.locationX; + } + const value = xValue / sliderWidth; + onSeekSliderSlidingComplete(value); + } + }; + + const onSliderLayout = (evt: any) => { + setSliderWidth(evt.nativeEvent.layout.width); + }; + + const generateRandomNumber = (max: number): number => { + var num = Math.floor(Math.random() * (max + 1)); + return num === currentIndex ? generateRandomNumber(max) : num; + }; + + const handleEndedPlaybackState = async () => { + if (playbackState === 'ENDED') { + let pause = await pauseAsync(); + if (pause) { + if (shuffle) { + setCurrentIndex(generateRandomNumber(cueList.length)); + } + return playNext(); + } + } + }; + + const disablePlaybackStatusUpdate = () => { + if ( + playbackState === 'ENDED' || + playbackState === 'LOADING' || + seekState === 'SEEKING' || + seekState === 'SEEKED' + ) { + return true; + } + return false; + }; + + const onPlaybackStatusUpdate = (status: any) => { + if (!status.isLoaded) { + if (status.error) { + const errorMsg = `Encountered a fatal error during playback: ${status.error}`; + setError(errorMsg); + return setPlaybackState('ERROR'); + } + return; + } + + if (networkState === 'none' && status.isBuffering) { + setPlaybackState('ERROR'); + setError( + 'You are probably offline. Please make sure you are connected to the Internet to watch this video' + ); + return; + } + + if (status.isPlaying && !status.isBuffering) { + setPlaybackInstancePosition(status.positionMillis); + setPlaybackInstanceDuration(status.durationMillis); + setSeekPosition(playbackInstancePosition / playbackInstanceDuration); + } + + setShouldPlay(status.shouldPlay); + + handleSetPlaybackState(getPlaybackStateFromStatus(status)); + }; + + const handleSetPlaybackState = async (playbackState: PlaybackState) => { + if (playbackState !== playbackState) { + setPlaybackState(playbackState); + handleEndedPlaybackState(); + setLastPlaybackState(Date.now()); + } + }; + + const getPlaybackStateFromStatus = (status: any) => { + if (status.didJustFinish && !status.isLooping) { + return 'ENDED'; + } + + if (status.isPlaying) { + return 'PLAYING'; + } + + if (status.isBuffering) { + return 'BUFFERING'; + } + + return 'PAUSED'; + }; + + return { + entry, + setShow, + getPlaybackStateFromStatus, + handleSetPlaybackState, + onPlaybackStatusUpdate, + disablePlaybackStatusUpdate, + handleEndedPlaybackState, + onSliderLayout, + onSeekBarTap, + onSeekSliderSlidingComplete, + onSeekSliderValueChange, + positionDisplay, + durationDisplay, + playPrev, + updateTabBarBottomPosition, + hidePlayer, + toggleShuffle, + playNext, + shuffle, + fullscreen, + presentFullscreenPlayer, + dismissFullscreenPlayer, + playbackState, + error, + seekState, + togglePlay, + show, + isPlaying, + pauseAsync, + playAsync, + toggleLoop, + loop, + replay, + seekPosition, + setSliding, + streamUrl, + mountVideo, + onFullscreenUpdate, + onError, + handleSetPlaylistMode, + loadAndPlay, + setPlaylistModeFromArray, + showMiniPlayer, + tabBarBottom, + setControlsState, + positionMillis, + setPositionMillis, + lastPlaybackState, + setLastPlaybackState, + setNetworkState, + refreshEntry, + disablePlaylistMode, + loadPlayAndUnshiftToCueList, + unmountMiniPlayer, + mountMiniPlayer, + loadPlayAndPushToCueList, + handleSetPlaybackInstance, + }; +}; diff --git a/src/stores/profile.ts b/src/stores/profile.ts new file mode 100644 index 0000000..71ecf0a --- /dev/null +++ b/src/stores/profile.ts @@ -0,0 +1,50 @@ +import { User, Entry } from '../models'; +import { entriesBackend } from '../api/entries'; +import { atom, useRecoilState } from 'recoil'; + +const profileUserAtom = atom({ + key: 'profileUser', + default: null, +}); +const profileEntriesAtom = atom({ + key: 'profileEntries', + default: [], +}); +const loadingEntriesAtom = atom({ + key: 'loadingEntries', + default: false, +}); + +export const ProfileStore = () => { + const [user, setUser] = useRecoilState(profileUserAtom); + const [profileEntries, setProfileEntries] = useRecoilState( + profileEntriesAtom + ); + const [loadingEntries, setLoadingEntries] = useRecoilState( + loadingEntriesAtom + ); + + const getProfileInfo = async (user: User) => { + if (!user) return; + setUser(user); + return getUserEntries(user.id as string); + }; + + const getUserEntries = async (userId: string) => { + setLoadingEntries(true); + setProfileEntries([]); + const entries = await entriesBackend.getByUserId(userId); + setLoadingEntries(false); + return setProfileEntries(entries); + }; + + return { + user, + profileEntries, + loadingEntries, + getProfileInfo, + getUserEntries, + }; +}; + +export default ProfileStore; diff --git a/src/stores/search.ts b/src/stores/search.ts new file mode 100644 index 0000000..5375c70 --- /dev/null +++ b/src/stores/search.ts @@ -0,0 +1,205 @@ +import { Entry } from '../models/entry.model'; +import { User } from '../models/user.model'; +import { entriesBackend } from '../api/entries'; +import { atom, selector, useRecoilState, useRecoilValue } from 'recoil'; +import { usersBackend } from '../api/users'; +import { useEffect } from 'react'; + +const debounce = require('lodash.debounce'); + +export const searchingAtom = atom({ + key: 'searching', + default: false, +}); +export const loadingRecentSearchesAtom = atom({ + key: 'loadingRecentSearches', + default: false, +}); +export const loadingTopSearchesAtom = atom({ + key: 'loadingTopSearches', + default: false, +}); +export const loadingRecentlyAddedAtom = atom({ + key: 'loadingRecentlyAdded', + default: false, +}); +export const loadingTopChartAtom = atom({ + key: 'loadingTopChart', + default: false, +}); +export const queryAtom = atom<{ type: 'entries' | 'users'; q: string }>({ + key: 'query', + default: { + type: 'entries', + q: '', + }, +}); +export const entriesAtom = atom({ + key: 'entries', + default: [], +}); +export const recentlyAddedAtom = atom({ + key: 'recentlyAdded', + default: [], +}); + +export const hasMoreRecentlyAddedAtom = atom({ + key: 'hasMoreRecentlyAdded', + default: true, +}); + +export const topChartAtom = atom({ + key: 'topChart', + default: [], +}); + +export const hasMoreTopChartAtom = atom({ + key: 'hasMoreTopChart', + default: true, +}); + +export const activeSearchAtom = selector({ + key: 'activeSearch', + get: ({ get }) => { + const { q } = get(queryAtom); + return !!q; + }, +}); + +export const usersAtom = atom({ + key: 'users', + default: [], +}); +export const searchingUsersAtom = atom({ + key: 'searchingUsers', + default: false, +}); +export const queryUsersAtom = atom({ + key: 'queryUsers', + default: '', +}); + +export const SearchStore = () => { + const [entries, setEntries] = useRecoilState(entriesAtom); + const [searching, setSearching] = useRecoilState(searchingAtom); + const [topChart, setTopChart] = useRecoilState(topChartAtom); + const [recentlyAdded, setRecentlyAdded] = useRecoilState(recentlyAddedAtom); + + const [loadingTopChart, setLoadingTopChart] = useRecoilState( + loadingTopChartAtom + ); + const [hasMoreTopChart, setHasMoreTopChart] = useRecoilState( + hasMoreTopChartAtom + ); + const [hasMoreRecentlyAdded, setHasMoreRecentlyAdded] = useRecoilState( + hasMoreRecentlyAddedAtom + ); + const [loadingRecentlyAdded, setLoadingRecentlyAdded] = useRecoilState( + loadingRecentlyAddedAtom + ); + + const [users, setUsers] = useRecoilState(usersAtom); + const [query, setQuery] = useRecoilState(queryAtom); + const active = useRecoilValue(activeSearchAtom); + + const search = (query: string) => { + setQuery((oldState) => ({ ...oldState, q: query })); + }; + + const updateSearchType = (type: 'entries' | 'users') => { + setQuery((oldState) => ({ ...oldState, type })); + }; + + const searchUsers = (q: string) => { + usersBackend.search(q).then((users) => { + setUsers(users); + setSearching(false); + }); + }; + + const searchEntries = (q: string) => { + return entriesBackend.search(q).then((results) => { + let entries: Entry[] = results.map((result: any) => new Entry(result)); + setEntries(entries); + setSearching(false); + }); + }; + + const debouncedEntriesSearch = debounce(searchEntries, 400); + const debouncedUsersSearch = debounce(searchUsers, 400); + + useEffect(() => { + if (query.type === 'users') { + debouncedUsersSearch(query.q); + } else { + debouncedEntriesSearch(query.q); + } + }, [query]); + + const getTopChart = () => { + setLoadingTopChart(true); + return entriesBackend.getTopChart().then((entries) => { + setTopChart(entries); + setLoadingTopChart(false); + }); + }; + + const loadMoreTopChart = (page: number) => { + setLoadingTopChart(true); + return entriesBackend.getTopChart(page).then((entries) => { + if (entries.length == 0) { + setHasMoreTopChart(false); + setLoadingTopChart(false); + return; + } + setTopChart([...topChart, ...entries]); + setLoadingTopChart(false); + return topChart; + }); + }; + + const getRecentlyAdded = () => { + setLoadingRecentlyAdded(true); + return entriesBackend.getRecentlyAdded().then((entries) => { + setRecentlyAdded(entries); + setLoadingRecentlyAdded(false); + return recentlyAdded; + }); + }; + + const loadMoreRecentlyAdded = (page: number) => { + setLoadingRecentlyAdded(true); + return entriesBackend.getRecentlyAdded(page).then((entries) => { + if (entries.length == 0) { + setHasMoreRecentlyAdded(false); + setLoadingRecentlyAdded(false); + return; + } + setRecentlyAdded([...recentlyAdded, ...entries]); + setLoadingRecentlyAdded(false); + return recentlyAdded; + }); + }; + + return { + searchEntries, + getTopChart, + loadMoreTopChart, + getRecentlyAdded, + loadMoreRecentlyAdded, + entries, + searching, + topChart, + recentlyAdded, + loadingTopChart, + hasMoreTopChart, + hasMoreRecentlyAdded, + loadingRecentlyAdded, + users, + query, + searchUsers, + updateSearchType, + search, + active, + }; +}; diff --git a/src/stores/session.ts b/src/stores/session.ts new file mode 100644 index 0000000..fa75e0e --- /dev/null +++ b/src/stores/session.ts @@ -0,0 +1,54 @@ +import { User } from '../models'; +import { + getAuthenticatedUser, + requestToken, + signIn as sendSignIn, + signUp as sendSignUp, +} from '../api/user'; +import { SignUpForm } from '../types'; +import { userAtom } from '../atoms/atoms'; +import { useRecoilState } from 'recoil'; + +export const SessionStore = () => { + const [user, setUser] = useRecoilState(userAtom); + + async function signUp(payload: SignUpForm) { + let userPayload = await sendSignUp(payload); + return setUser(new User(userPayload)); + } + + async function signIn(token?: string, uid?: string, xdr = '') { + let userPayload = await sendSignIn(token, uid, xdr); + if (userPayload) { + return setUser(new User(userPayload)); + } + return null; + } + + // public forceSignOutDisposer = observe(forceSignOut, ({ object }) => { + // if (object.value) { + // this.signOut(); + // } + // }); + + async function signOut() { + return setUser(null); + } + + async function refreshUser() { + try { + let userPayload = await getAuthenticatedUser(); + if (userPayload) { + let userPayloadClone = Object.assign({}, userPayload); + userPayloadClone.jwt = user?.jwt; + setUser(new User(userPayloadClone)); + return user; + } + } catch (e) { + console.info(e); + } + return await signOut(); + } + + return { requestToken, signUp, signIn, signOut, refreshUser, user }; +}; diff --git a/src/stores/user-entries.ts b/src/stores/user-entries.ts new file mode 100644 index 0000000..c452a48 --- /dev/null +++ b/src/stores/user-entries.ts @@ -0,0 +1,26 @@ +import { entriesBackend } from '../api/entries'; +import { Entry } from '../models'; +import { userAtom } from '../atoms/atoms'; +import { useRecoilValue } from 'recoil'; + +export const UserEntriesStore = () => { + const user = useRecoilValue(userAtom); + + let entries: Entry[] = []; + let loading: boolean = false; + + const refreshEntries = async () => { + if (!user) { + return; + } + if (!user.id) { + return; + } + loading = true; + + const res = await entriesBackend.getByUserId(user.id); + loading = false; + entries = res ? res : []; + }; + return { entries, loading, refreshEntries }; +}; diff --git a/src/stores/wallet-connect.ts b/src/stores/wallet-connect.ts new file mode 100644 index 0000000..16da5ea --- /dev/null +++ b/src/stores/wallet-connect.ts @@ -0,0 +1,188 @@ +import WalletConnect, { CLIENT_EVENTS } from '@walletconnect/client'; +import { atom, useRecoilState } from 'recoil'; +import { Session } from '@walletconnect/client/dist/cjs/controllers'; +import { useEffect } from 'react'; + +const stellarMeta = { + chainName: 'stellar:pubnet', + methods: ['stellar_signAndSubmitXDR', 'stellar_signXDR'], +}; + +const clientAtom = atom({ + key: 'client', + default: null, +}); + +const uriAtom = atom({ + key: 'uri', + default: null, +}); + +const stateAtom = atom< + | 'disconnected' + | 'paring-proposal' + | 'paring-created' + | 'session-proposal' + | 'session-created' +>({ + key: 'state', + default: 'disconnected', +}); + +const publicKeyAtom = atom({ + key: 'publicKey', +}); + +const sessionAtom = atom({ + key: 'session', +}); + +export const WalletConnectStore = () => { + const [client, setClient] = useRecoilState(clientAtom); + const [uri, setUri] = useRecoilState(uriAtom); + const [state, setState] = useRecoilState(stateAtom); + const [publicKey, setPublicKey] = useRecoilState(publicKeyAtom); + const [session, setSession] = useRecoilState(sessionAtom); + + const init = async () => { + if (client) return; + const result = await WalletConnect.init({ + projectId: '422a527ddc3ed4c5fff60954fcc8ed83', + metadata: { + name: 'Skyhitz', + description: 'Skyhitz', + url: 'https://skyhitz.io', + icons: ['https://skyhitz.io/img/icon-512.png'], + }, + }); + + setClient(result); + if (result.session) { + handleSetSession(result.session); + } + + subscribeToEvents(); + }; + + useEffect(() => { + init(); + }); + + const clearState = () => { + setState('disconnected'); + setPublicKey(''); + handleSetSession(null); + setClient(null); + }; + + const handleSetSession = (session) => { + setSession(session); + const { state } = session; + const [stellarAccount] = state.accounts; + setPublicKey(stellarAccount.replace(`${stellarMeta.chainName}:`, '')); + setState('session-created'); + return publicKey; + }; + + const connect = async () => { + try { + return handleSetSession( + await client?.connect({ + permissions: { + blockchain: { + chains: [stellarMeta.chainName], + }, + jsonrpc: { + methods: stellarMeta.methods, + }, + }, + }) + ); + } catch (e) { + console.log('catched error on reject:', e); + setState('disconnected'); + } + + return publicKey; + }; + + const disconnect = async () => { + await client?.disconnect({ + topic: session.topics[0], + reason: { + code: 1, + message: 'Logged out', + }, + }); + await clearState(); + }; + + const subscribeToEvents = () => { + console.log('subscribed to events'); + client?.on(CLIENT_EVENTS.pairing.proposal, async (proposal) => { + const { uri } = proposal.signal.params; + console.log('pairing proposal'); + setUri(uri); + setState('paring-proposal'); + }); + + client?.on(CLIENT_EVENTS.pairing.created, async (proposal) => { + setUri(null); + setState('paring-created'); + }); + + client?.on(CLIENT_EVENTS.session.proposal, async (proposal) => { + setState('session-proposal'); + }); + + client?.on(CLIENT_EVENTS.session.created, async (proposal) => { + setState('session-created'); + }); + + client?.on(CLIENT_EVENTS.session.deleted, (session) => { + console.log(session); + clearState(); + }); + }; + + const signXdr = (xdr) => { + return client?.request({ + topic: session.topics[0], + chainId: stellarMeta.chainName, + request: { + jsonrpc: '2.0', + method: 'stellar_signXDR', + params: { + xdr, + }, + } as any, + }); + }; + + const signAndSubmitXdr = (xdr) => { + return client?.request({ + topic: session.topics[0], + chainId: stellarMeta.chainName, + request: { + jsonrpc: '2.0', + method: 'stellar_signAndSubmitXDR', + params: { + xdr, + }, + } as any, + }); + }; + return { + signAndSubmitXdr, + signXdr, + subscribeToEvents, + disconnect, + connect, + clearState, + init, + publicKey, + uri, + session, + state, + }; +}; diff --git a/skyhitz-common/src/types/index.ts b/src/types/index.ts similarity index 100% rename from skyhitz-common/src/types/index.ts rename to src/types/index.ts diff --git a/modules/ui/ArrowDownBackBtn.tsx b/src/ui/ArrowDownBackBtn.tsx similarity index 79% rename from modules/ui/ArrowDownBackBtn.tsx rename to src/ui/ArrowDownBackBtn.tsx index 138b65f..2cf57ef 100644 --- a/modules/ui/ArrowDownBackBtn.tsx +++ b/src/ui/ArrowDownBackBtn.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Pressable, StyleSheet } from 'react-native'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; import { useNavigation } from '@react-navigation/native'; -import cursorPointer from 'app/constants/CursorPointer'; -import ChevronDownIcon from 'app/modules/ui/icons/chevron-down'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import ChevronDownIcon from 'app/src/ui/icons/chevron-down'; export default () => { const { goBack } = useNavigation(); diff --git a/modules/ui/BackgroundImage.tsx b/src/ui/BackgroundImage.tsx similarity index 100% rename from modules/ui/BackgroundImage.tsx rename to src/ui/BackgroundImage.tsx diff --git a/modules/ui/BottomPlaceholder.tsx b/src/ui/BottomPlaceholder.tsx similarity index 100% rename from modules/ui/BottomPlaceholder.tsx rename to src/ui/BottomPlaceholder.tsx diff --git a/modules/ui/BuyOptionsModal.tsx b/src/ui/BuyOptionsModal.tsx similarity index 77% rename from modules/ui/BuyOptionsModal.tsx rename to src/ui/BuyOptionsModal.tsx index d4582a7..aa8a6f8 100644 --- a/modules/ui/BuyOptionsModal.tsx +++ b/src/ui/BuyOptionsModal.tsx @@ -1,13 +1,15 @@ import React, { useState } from 'react'; import { View, StyleSheet, Text, ActivityIndicator } from 'react-native'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; import LargeBtn from './LargeBtn'; -import { observer } from 'mobx-react'; -import { Stores } from 'app/functions/Stores'; import { useNavigation } from '@react-navigation/core'; import { useLinkTo } from '@react-navigation/native'; +import { UserEntriesStore } from '../stores/user-entries'; +import { PaymentsStore } from '../stores/payments'; +import { WalletConnectStore } from '../stores/wallet-connect'; +import { PlayerStore } from '../stores/player'; -export default observer(({ route }) => { +export default ({ route }) => { const { entry, priceInfo } = route.params; const [submitting, setSubmitting] = useState(false); const [ @@ -17,17 +19,14 @@ export default observer(({ route }) => { const { goBack } = useNavigation(); const linkTo = useLinkTo(); - const { - paymentsStore, - userEntriesStore, - entriesSearchStore, - playerStore, - walletConnectStore, - } = Stores(); + const { refreshEntry } = PlayerStore(); + const { signAndSubmitXdr } = WalletConnectStore(); + const { buyEntry, refreshSubscription, credits } = PaymentsStore(); + const { refreshEntries } = UserEntriesStore(); - const buyEntry = async (id: string) => { + const handleBuyEntry = async (id: string) => { setSubmitting(true); - let { xdr, success, submitted } = await paymentsStore.buyEntry( + let { xdr, success, submitted } = await buyEntry( id, priceInfo.amount, priceInfo.price @@ -36,14 +35,10 @@ export default observer(({ route }) => { if (xdr && success) { if (!submitted) { setMustSignAndSubmitWithWalletConnect(true); - await walletConnectStore.signAndSubmitXdr(xdr); + await signAndSubmitXdr(xdr); } - await Promise.all([ - await userEntriesStore.refreshEntries(), - await entriesSearchStore.getRecentSearches(), - await paymentsStore.refreshSubscription(), - ]); - playerStore.refreshEntry(); + await Promise.all([await refreshEntries(), await refreshSubscription()]); + refreshEntry(); setSubmitting(false); goBack(); linkTo('/dashboard/profile'); @@ -51,7 +46,7 @@ export default observer(({ route }) => { } }; - if (!paymentsStore.credits || paymentsStore.credits < entry.price) { + if (!credits || credits < entry.price) { return ( @@ -92,11 +87,11 @@ export default observer(({ route }) => { goBack()} /> - buyEntry(entry.id)} /> + handleBuyEntry(entry.id)} /> ); -}); +}; const styles = StyleSheet.create({ container: { diff --git a/modules/ui/CancelEditBtn.tsx b/src/ui/CancelEditBtn.tsx similarity index 84% rename from modules/ui/CancelEditBtn.tsx rename to src/ui/CancelEditBtn.tsx index 24ff036..b431d90 100644 --- a/modules/ui/CancelEditBtn.tsx +++ b/src/ui/CancelEditBtn.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { Pressable, StyleSheet, Text } from 'react-native'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; import { useLinkTo } from '@react-navigation/native'; -import cursorPointer from 'app/constants/CursorPointer'; +import cursorPointer from 'app/src/constants/CursorPointer'; const CancelEditBtn = () => { const linkTo = useLinkTo(); diff --git a/modules/ui/Divider.tsx b/src/ui/Divider.tsx similarity index 87% rename from modules/ui/Divider.tsx rename to src/ui/Divider.tsx index aaf594f..835e076 100644 --- a/modules/ui/Divider.tsx +++ b/src/ui/Divider.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, StyleSheet } from 'react-native'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; export default class Divider extends React.Component { render() { diff --git a/src/ui/DoneEditBtn.tsx b/src/ui/DoneEditBtn.tsx new file mode 100644 index 0000000..a1132da --- /dev/null +++ b/src/ui/DoneEditBtn.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { Pressable, StyleSheet, Text } from 'react-native'; +import Colors from 'app/src/constants/Colors'; +import { useLinkTo } from '@react-navigation/native'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import { updateUser } from '../api/user'; +import { SessionStore } from '../stores/session'; +import { canUpdateProfileAtom, profileAtom } from '../atoms/atoms'; +import { useRecoilState, useRecoilValue } from 'recoil'; + +const DoneEditBtn = () => { + const linkTo = useLinkTo(); + + const [ + { avatarUrl, displayName, description, username, email }, + setEditProfile, + ] = useRecoilState(profileAtom); + + const canUpdate = useRecoilValue(canUpdateProfileAtom); + const { refreshUser } = SessionStore(); + + const updateProfile = async () => { + let user; + try { + user = await updateUser( + avatarUrl as string, + displayName as string, + description as string, + username as string, + email as string + ); + } catch (e) { + setEditProfile((oldState) => ({ + ...oldState, + uploadError: (e as any).toString(), + })); + return; + } + if (user) { + return await refreshUser(); + } + }; + + const closeProfileModal = () => { + updateProfile(); + linkTo('/dashboard/profile'); + }; + + return ( + + Done + + ); +}; + +export default DoneEditBtn; + +const styles = StyleSheet.create({ + btn: { + paddingRight: 10, + }, + white: { + color: Colors.white, + }, +}); diff --git a/modules/ui/EditBtn.tsx b/src/ui/EditBtn.tsx similarity index 79% rename from modules/ui/EditBtn.tsx rename to src/ui/EditBtn.tsx index 9503e82..6e98d19 100644 --- a/modules/ui/EditBtn.tsx +++ b/src/ui/EditBtn.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Pressable, StyleSheet } from 'react-native'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; import { useLinkTo } from '@react-navigation/native'; -import cursorPointer from 'app/constants/CursorPointer'; -import CogIcon from 'app/modules/ui/icons/cog'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import CogIcon from 'app/src/ui/icons/cog'; export default ({ customStyles }) => { const linkTo = useLinkTo(); diff --git a/modules/ui/EntryChartRow.tsx b/src/ui/EntryChartRow.tsx similarity index 94% rename from modules/ui/EntryChartRow.tsx rename to src/ui/EntryChartRow.tsx index 3787aa9..0dc611d 100644 --- a/modules/ui/EntryChartRow.tsx +++ b/src/ui/EntryChartRow.tsx @@ -7,11 +7,11 @@ import { Pressable, Platform, } from 'react-native'; -import Layout from 'app/constants/Layout'; -import Colors from 'app/constants/Colors'; -import ThreeDots from 'app/modules/ui/ThreeDots'; +import Layout from 'app/src/constants/Layout'; +import Colors from 'app/src/constants/Colors'; +import ThreeDots from 'app/src/ui/ThreeDots'; import { CommonActions, useNavigation } from '@react-navigation/native'; -import cursorPointer from 'app/constants/CursorPointer'; +import cursorPointer from 'app/src/constants/CursorPointer'; import LikeOptionRow from '../search/LikeOptionRow'; import EntryPrice from './EntryPrice'; diff --git a/modules/ui/EntryPrice.tsx b/src/ui/EntryPrice.tsx similarity index 77% rename from modules/ui/EntryPrice.tsx rename to src/ui/EntryPrice.tsx index 15b12ca..d4c5f3e 100644 --- a/modules/ui/EntryPrice.tsx +++ b/src/ui/EntryPrice.tsx @@ -1,17 +1,17 @@ import React, { useEffect, useState } from 'react'; import { StyleSheet, View, Text } from 'react-native'; -import DollarIcon from 'app/modules/ui/icons/dollar'; -import Colors from 'app/constants/Colors'; -import { Stores } from 'app/functions/Stores'; +import DollarIcon from 'app/src/ui/icons/dollar'; +import Colors from 'app/src/constants/Colors'; import { A } from '@expo/html-elements'; -import { stellarAssetLink } from 'app/functions/utils'; +import { stellarAssetLink } from 'app/src/functions/utils'; +import { PaymentsStore } from '../stores/payments'; function EntryPrice({ code, issuer }) { const [value, setValue] = useState({ price: 0, amount: 0 }); - const { paymentsStore } = Stores(); + const { fetchAndCachePrice } = PaymentsStore(); const handleFetchPrice = async () => { - setValue(await paymentsStore.fetchAndCachePrice(code, issuer)); + setValue(await fetchAndCachePrice(code, issuer)); }; useEffect(() => { diff --git a/modules/ui/EntryRow.tsx b/src/ui/EntryRow.tsx similarity index 93% rename from modules/ui/EntryRow.tsx rename to src/ui/EntryRow.tsx index d604048..7719913 100644 --- a/modules/ui/EntryRow.tsx +++ b/src/ui/EntryRow.tsx @@ -7,11 +7,11 @@ import { Platform, Pressable, } from 'react-native'; -import Layout from 'app/constants/Layout'; -import Colors from 'app/constants/Colors'; -import ThreeDots from 'app/modules/ui/ThreeDots'; +import Layout from 'app/src/constants/Layout'; +import Colors from 'app/src/constants/Colors'; +import ThreeDots from 'app/src/ui/ThreeDots'; import { CommonActions, useNavigation } from '@react-navigation/native'; -import cursorPointer from 'app/constants/CursorPointer'; +import cursorPointer from 'app/src/constants/CursorPointer'; import EntryPrice from './EntryPrice'; import LikeOptionRow from '../search/LikeOptionRow'; diff --git a/modules/ui/Input.tsx b/src/ui/Input.tsx similarity index 98% rename from modules/ui/Input.tsx rename to src/ui/Input.tsx index 356f3cc..ae82a04 100644 --- a/modules/ui/Input.tsx +++ b/src/ui/Input.tsx @@ -7,7 +7,7 @@ import { Dimensions, Animated, } from 'react-native'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; const SCREEN_WIDTH = Dimensions.get('window').width; diff --git a/modules/ui/LargeBtn.tsx b/src/ui/LargeBtn.tsx similarity index 92% rename from modules/ui/LargeBtn.tsx rename to src/ui/LargeBtn.tsx index 928f860..031eef4 100644 --- a/modules/ui/LargeBtn.tsx +++ b/src/ui/LargeBtn.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Pressable, StyleSheet, Text } from 'react-native'; -import Colors from 'app/constants/Colors'; -import cursorPointer from 'app/constants/CursorPointer'; +import Colors from 'app/src/constants/Colors'; +import cursorPointer from 'app/src/constants/CursorPointer'; import tw from 'twin.macro'; export default class LargeBtn extends React.Component { diff --git a/modules/ui/Letter.tsx b/src/ui/Letter.tsx similarity index 100% rename from modules/ui/Letter.tsx rename to src/ui/Letter.tsx diff --git a/src/ui/LogOutBtn.tsx b/src/ui/LogOutBtn.tsx new file mode 100644 index 0000000..4240345 --- /dev/null +++ b/src/ui/LogOutBtn.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { Pressable, StyleSheet } from 'react-native'; +import Colors from 'app/src/constants/Colors'; +import LogOutIcon from 'app/src/ui/icons/logout'; +import { useLinkTo } from '@react-navigation/native'; +import { LikesStore } from '../stores/likes'; +import { SessionStore } from '../stores/session'; +import { WalletConnectStore } from '../stores/wallet-connect'; + +export default () => { + const linkTo = useLinkTo(); + const { disconnect } = WalletConnectStore(); + const { signOut } = SessionStore(); + const { clearLikes } = LikesStore(); + + const handleLogOut = async () => { + await disconnect(); + await signOut(); + linkTo('/'); + clearLikes(); + }; + + return ( + + + + ); +}; + +const styles = StyleSheet.create({ + btn: { + paddingRight: 10, + }, +}); diff --git a/modules/ui/MediaQueries.tsx b/src/ui/MediaQueries.tsx similarity index 100% rename from modules/ui/MediaQueries.tsx rename to src/ui/MediaQueries.tsx diff --git a/modules/ui/ResponsiveLayout.tsx b/src/ui/ResponsiveLayout.tsx similarity index 100% rename from modules/ui/ResponsiveLayout.tsx rename to src/ui/ResponsiveLayout.tsx diff --git a/modules/ui/SearchingLoader.tsx b/src/ui/SearchingLoader.tsx similarity index 96% rename from modules/ui/SearchingLoader.tsx rename to src/ui/SearchingLoader.tsx index af0165a..d79c869 100644 --- a/modules/ui/SearchingLoader.tsx +++ b/src/ui/SearchingLoader.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { StyleSheet, View, Text, ActivityIndicator } from 'react-native'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; const SearchingLoader = (searching: any, query?: any) => { if (searching) { diff --git a/modules/ui/TextWithLetterSpacing.tsx b/src/ui/TextWithLetterSpacing.tsx similarity index 94% rename from modules/ui/TextWithLetterSpacing.tsx rename to src/ui/TextWithLetterSpacing.tsx index 111e3fb..040d868 100644 --- a/modules/ui/TextWithLetterSpacing.tsx +++ b/src/ui/TextWithLetterSpacing.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; -import { Letter } from 'app/modules/ui/Letter'; +import { Letter } from 'app/src/ui/Letter'; const spacingForLetterIndex = (letters: any, index: any, spacing: any) => letters.length - 1 === index ? 0 : spacing; diff --git a/modules/ui/ThreeDots.tsx b/src/ui/ThreeDots.tsx similarity index 86% rename from modules/ui/ThreeDots.tsx rename to src/ui/ThreeDots.tsx index 205517f..3801dd3 100644 --- a/modules/ui/ThreeDots.tsx +++ b/src/ui/ThreeDots.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { StyleSheet, View, Pressable } from 'react-native'; -import Colors from 'app/constants/Colors'; -import cursorPointer from 'app/constants/CursorPointer'; +import Colors from 'app/src/constants/Colors'; +import cursorPointer from 'app/src/constants/CursorPointer'; var styles = StyleSheet.create({ dot: { diff --git a/modules/ui/UserAvatar.tsx b/src/ui/UserAvatar.tsx similarity index 97% rename from modules/ui/UserAvatar.tsx rename to src/ui/UserAvatar.tsx index 4cbcdbd..b558ae0 100644 --- a/modules/ui/UserAvatar.tsx +++ b/src/ui/UserAvatar.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { StyleSheet, View, Text, Image, ActivityIndicator } from 'react-native'; -import Colors from 'app/constants/Colors'; +import Colors from 'app/src/constants/Colors'; export class UserAvatar extends React.Component { state = { diff --git a/modules/ui/UserRow.tsx b/src/ui/UserRow.tsx similarity index 78% rename from modules/ui/UserRow.tsx rename to src/ui/UserRow.tsx index 24b993e..6a40869 100644 --- a/modules/ui/UserRow.tsx +++ b/src/ui/UserRow.tsx @@ -1,21 +1,22 @@ import React from 'react'; import { StyleSheet, View, Text, Pressable } from 'react-native'; -import Colors from 'app/constants/Colors'; -import { UserAvatar } from 'app/modules/ui/UserAvatar'; -import { Stores } from 'app/functions/Stores'; -import { observer } from 'mobx-react'; +import Colors from 'app/src/constants/Colors'; +import { UserAvatar } from 'app/src/ui/UserAvatar'; import { CommonActions, useNavigation } from '@react-navigation/native'; +import ProfileStore from '../stores/profile'; +import { PlayerStore } from '../stores/player'; -export default observer(({ user }) => { - const { profileStore, playerStore } = Stores(); +export default ({ user }) => { + const { handleSetPlaylistMode } = PlayerStore(); + const { getProfileInfo } = ProfileStore(); const { dispatch } = useNavigation(); return ( { - profileStore.getProfileInfo(user).then((entries: any) => { - playerStore.setPlaylistMode(entries); + getProfileInfo(user).then((entries: any) => { + handleSetPlaylistMode(entries); }); dispatch( CommonActions.navigate({ @@ -38,7 +39,7 @@ export default observer(({ user }) => { ); -}); +}; let styles = StyleSheet.create({ rowWrap: { diff --git a/modules/ui/ViewPropTypes.tsx b/src/ui/ViewPropTypes.tsx similarity index 100% rename from modules/ui/ViewPropTypes.tsx rename to src/ui/ViewPropTypes.tsx diff --git a/modules/ui/buy-btn/BuyBtn.tsx b/src/ui/buy-btn/BuyBtn.tsx similarity index 82% rename from modules/ui/buy-btn/BuyBtn.tsx rename to src/ui/buy-btn/BuyBtn.tsx index 50ddd49..7aeb0c1 100644 --- a/modules/ui/buy-btn/BuyBtn.tsx +++ b/src/ui/buy-btn/BuyBtn.tsx @@ -1,17 +1,16 @@ import React, { useEffect, useState } from 'react'; import { StyleSheet, Text, View, Pressable } from 'react-native'; -import Colors from 'app/constants/Colors'; -import { Stores } from 'app/functions/Stores'; +import Colors from 'app/src/constants/Colors'; import { CommonActions, useNavigation } from '@react-navigation/native'; -import { observer } from 'mobx-react'; -import cursorPointer from 'app/constants/CursorPointer'; -import DollarIcon from 'app/modules/ui/icons/dollar'; +import cursorPointer from 'app/src/constants/CursorPointer'; +import DollarIcon from 'app/src/ui/icons/dollar'; import tw from 'twin.macro'; +import { PaymentsStore } from 'app/src/stores/payments'; const Placeholder = () => ; -export default observer(({ entry }) => { - const { paymentsStore } = Stores(); +export default ({ entry }) => { + const { fetchAndCachePrice, refreshSubscription } = PaymentsStore(); const { dispatch } = useNavigation(); const [priceInfo, setPriceInfo] = useState({ @@ -32,7 +31,7 @@ export default observer(({ entry }) => { }; const getPriceInfo = async () => { - const { price, amount } = await paymentsStore.fetchAndCachePrice( + const { price, amount } = await fetchAndCachePrice( entry.code, entry.issuer ); @@ -42,7 +41,7 @@ export default observer(({ entry }) => { useEffect(() => { if (entry && entry.id) { getPriceInfo(); - paymentsStore.refreshSubscription(); + refreshSubscription(); } }, [entry]); @@ -64,7 +63,7 @@ export default observer(({ entry }) => { ); -}); +}; var styles = StyleSheet.create({ wrap: { diff --git a/modules/ui/icons/account-box.tsx b/src/ui/icons/account-box.tsx similarity index 100% rename from modules/ui/icons/account-box.tsx rename to src/ui/icons/account-box.tsx diff --git a/modules/ui/icons/check.tsx b/src/ui/icons/check.tsx similarity index 100% rename from modules/ui/icons/check.tsx rename to src/ui/icons/check.tsx diff --git a/modules/ui/icons/chevron-down.tsx b/src/ui/icons/chevron-down.tsx similarity index 100% rename from modules/ui/icons/chevron-down.tsx rename to src/ui/icons/chevron-down.tsx diff --git a/modules/ui/icons/chevron-left.tsx b/src/ui/icons/chevron-left.tsx similarity index 100% rename from modules/ui/icons/chevron-left.tsx rename to src/ui/icons/chevron-left.tsx diff --git a/modules/ui/icons/chevron-right.tsx b/src/ui/icons/chevron-right.tsx similarity index 100% rename from modules/ui/icons/chevron-right.tsx rename to src/ui/icons/chevron-right.tsx diff --git a/modules/ui/icons/chevron-up.tsx b/src/ui/icons/chevron-up.tsx similarity index 100% rename from modules/ui/icons/chevron-up.tsx rename to src/ui/icons/chevron-up.tsx diff --git a/modules/ui/icons/circle.tsx b/src/ui/icons/circle.tsx similarity index 100% rename from modules/ui/icons/circle.tsx rename to src/ui/icons/circle.tsx diff --git a/modules/ui/icons/cog.tsx b/src/ui/icons/cog.tsx similarity index 100% rename from modules/ui/icons/cog.tsx rename to src/ui/icons/cog.tsx diff --git a/modules/ui/icons/discord.tsx b/src/ui/icons/discord.tsx similarity index 100% rename from modules/ui/icons/discord.tsx rename to src/ui/icons/discord.tsx diff --git a/modules/ui/icons/dollar.tsx b/src/ui/icons/dollar.tsx similarity index 100% rename from modules/ui/icons/dollar.tsx rename to src/ui/icons/dollar.tsx diff --git a/modules/ui/icons/fullscreen-exit.tsx b/src/ui/icons/fullscreen-exit.tsx similarity index 100% rename from modules/ui/icons/fullscreen-exit.tsx rename to src/ui/icons/fullscreen-exit.tsx diff --git a/modules/ui/icons/fullscreen.tsx b/src/ui/icons/fullscreen.tsx similarity index 100% rename from modules/ui/icons/fullscreen.tsx rename to src/ui/icons/fullscreen.tsx diff --git a/modules/ui/icons/github.tsx b/src/ui/icons/github.tsx similarity index 100% rename from modules/ui/icons/github.tsx rename to src/ui/icons/github.tsx diff --git a/modules/ui/icons/info-circle.tsx b/src/ui/icons/info-circle.tsx similarity index 100% rename from modules/ui/icons/info-circle.tsx rename to src/ui/icons/info-circle.tsx diff --git a/modules/ui/icons/instagram.tsx b/src/ui/icons/instagram.tsx similarity index 100% rename from modules/ui/icons/instagram.tsx rename to src/ui/icons/instagram.tsx diff --git a/modules/ui/icons/key.tsx b/src/ui/icons/key.tsx similarity index 100% rename from modules/ui/icons/key.tsx rename to src/ui/icons/key.tsx diff --git a/modules/ui/icons/like.tsx b/src/ui/icons/like.tsx similarity index 100% rename from modules/ui/icons/like.tsx rename to src/ui/icons/like.tsx diff --git a/modules/ui/icons/logout.tsx b/src/ui/icons/logout.tsx similarity index 100% rename from modules/ui/icons/logout.tsx rename to src/ui/icons/logout.tsx diff --git a/modules/ui/icons/mail-outline.tsx b/src/ui/icons/mail-outline.tsx similarity index 100% rename from modules/ui/icons/mail-outline.tsx rename to src/ui/icons/mail-outline.tsx diff --git a/modules/ui/icons/money.tsx b/src/ui/icons/money.tsx similarity index 100% rename from modules/ui/icons/money.tsx rename to src/ui/icons/money.tsx diff --git a/modules/ui/icons/pause.tsx b/src/ui/icons/pause.tsx similarity index 100% rename from modules/ui/icons/pause.tsx rename to src/ui/icons/pause.tsx diff --git a/modules/ui/icons/person-outline.tsx b/src/ui/icons/person-outline.tsx similarity index 100% rename from modules/ui/icons/person-outline.tsx rename to src/ui/icons/person-outline.tsx diff --git a/modules/ui/icons/phone.tsx b/src/ui/icons/phone.tsx similarity index 100% rename from modules/ui/icons/phone.tsx rename to src/ui/icons/phone.tsx diff --git a/modules/ui/icons/pie.tsx b/src/ui/icons/pie.tsx similarity index 100% rename from modules/ui/icons/pie.tsx rename to src/ui/icons/pie.tsx diff --git a/modules/ui/icons/play.tsx b/src/ui/icons/play.tsx similarity index 100% rename from modules/ui/icons/play.tsx rename to src/ui/icons/play.tsx diff --git a/modules/ui/icons/remove.tsx b/src/ui/icons/remove.tsx similarity index 100% rename from modules/ui/icons/remove.tsx rename to src/ui/icons/remove.tsx diff --git a/modules/ui/icons/repeat.tsx b/src/ui/icons/repeat.tsx similarity index 100% rename from modules/ui/icons/repeat.tsx rename to src/ui/icons/repeat.tsx diff --git a/modules/ui/icons/search.tsx b/src/ui/icons/search.tsx similarity index 100% rename from modules/ui/icons/search.tsx rename to src/ui/icons/search.tsx diff --git a/modules/ui/icons/shuffle.tsx b/src/ui/icons/shuffle.tsx similarity index 100% rename from modules/ui/icons/shuffle.tsx rename to src/ui/icons/shuffle.tsx diff --git a/modules/ui/icons/skip-backward.tsx b/src/ui/icons/skip-backward.tsx similarity index 100% rename from modules/ui/icons/skip-backward.tsx rename to src/ui/icons/skip-backward.tsx diff --git a/modules/ui/icons/skip-forward.tsx b/src/ui/icons/skip-forward.tsx similarity index 100% rename from modules/ui/icons/skip-forward.tsx rename to src/ui/icons/skip-forward.tsx diff --git a/modules/ui/icons/star-border.tsx b/src/ui/icons/star-border.tsx similarity index 100% rename from modules/ui/icons/star-border.tsx rename to src/ui/icons/star-border.tsx diff --git a/modules/ui/icons/twitter.tsx b/src/ui/icons/twitter.tsx similarity index 100% rename from modules/ui/icons/twitter.tsx rename to src/ui/icons/twitter.tsx diff --git a/modules/ui/icons/upload.tsx b/src/ui/icons/upload.tsx similarity index 100% rename from modules/ui/icons/upload.tsx rename to src/ui/icons/upload.tsx diff --git a/modules/ui/icons/user.tsx b/src/ui/icons/user.tsx similarity index 100% rename from modules/ui/icons/user.tsx rename to src/ui/icons/user.tsx diff --git a/modules/ui/icons/wallet.tsx b/src/ui/icons/wallet.tsx similarity index 100% rename from modules/ui/icons/wallet.tsx rename to src/ui/icons/wallet.tsx diff --git a/modules/ui/icons/walletconnect-icon.tsx b/src/ui/icons/walletconnect-icon.tsx similarity index 100% rename from modules/ui/icons/walletconnect-icon.tsx rename to src/ui/icons/walletconnect-icon.tsx diff --git a/modules/ui/icons/x.tsx b/src/ui/icons/x.tsx similarity index 100% rename from modules/ui/icons/x.tsx rename to src/ui/icons/x.tsx diff --git a/modules/ui/searchbar/SearchBar.android.js b/src/ui/searchbar/SearchBar.android.js similarity index 95% rename from modules/ui/searchbar/SearchBar.android.js rename to src/ui/searchbar/SearchBar.android.js index 7c25e2a..f8a824e 100644 --- a/modules/ui/searchbar/SearchBar.android.js +++ b/src/ui/searchbar/SearchBar.android.js @@ -8,10 +8,10 @@ import { Platform, Pressable, } from 'react-native'; -import Input from 'app/modules/ui/Input'; -import Colors from 'app/constants/Colors'; -import SearchIcon from 'app/modules/ui/icons/search'; -import CloseIcon from 'app/modules/ui/icons/x'; +import Input from 'app/src/ui/Input'; +import Colors from 'app/src/constants/Colors'; +import SearchIcon from 'app/src/ui/icons/search'; +import CloseIcon from 'app/src/ui/icons/x'; class SearchBar extends Component { focus = () => { diff --git a/modules/ui/searchbar/SearchBar.ios.js b/src/ui/searchbar/SearchBar.ios.js similarity index 95% rename from modules/ui/searchbar/SearchBar.ios.js rename to src/ui/searchbar/SearchBar.ios.js index 3056de5..724dc59 100644 --- a/modules/ui/searchbar/SearchBar.ios.js +++ b/src/ui/searchbar/SearchBar.ios.js @@ -11,11 +11,11 @@ import { Platform, Pressable, } from 'react-native'; -import Colors from 'app/constants/Colors'; -import CloseIcon from 'app/modules/ui/icons/x'; -import SearchIcon from 'app/modules/ui/icons/search'; +import Colors from 'app/src/constants/Colors'; +import CloseIcon from 'app/src/ui/icons/x'; +import SearchIcon from 'app/src/ui/icons/search'; -import Input from 'app/modules/ui/Input'; +import Input from 'app/src/ui/Input'; const SCREEN_WIDTH = Dimensions.get('window').width; const IOS_GRAY = Colors.searchTextColor; diff --git a/modules/ui/searchbar/SearchBar.tsx b/src/ui/searchbar/SearchBar.tsx similarity index 100% rename from modules/ui/searchbar/SearchBar.tsx rename to src/ui/searchbar/SearchBar.tsx diff --git a/yarn.lock b/yarn.lock index 273f5e6..4637c6a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -151,24 +151,6 @@ dependencies: cross-fetch "3.0.4" -"@apollo/client@^3.5.6": - version "3.5.6" - resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.5.6.tgz#911929df073280689efd98e5603047b79e0c39a2" - integrity sha512-XHoouuEJ4L37mtfftcHHO1caCRrKKAofAwqRoq28UQIPMJk+e7n3X9OtRRNXKk/9tmhNkwelSary+EilfPwI7A== - dependencies: - "@graphql-typed-document-node/core" "^3.0.0" - "@wry/context" "^0.6.0" - "@wry/equality" "^0.5.0" - "@wry/trie" "^0.3.0" - graphql-tag "^2.12.3" - hoist-non-react-statics "^3.3.2" - optimism "^0.16.1" - prop-types "^15.7.2" - symbol-observable "^4.0.0" - ts-invariant "^0.9.4" - tslib "^2.3.0" - zen-observable-ts "^1.2.0" - "@babel/code-frame@7.10.4", "@babel/code-frame@~7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" @@ -2937,11 +2919,6 @@ webpack "4.43.0" webpack-manifest-plugin "~2.2.0" -"@graphql-typed-document-node/core@^3.0.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.1.tgz#076d78ce99822258cf813ecc1e7fa460fa74d052" - integrity sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg== - "@hapi/accept@5.0.2": version "5.0.2" resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.2.tgz#ab7043b037e68b722f93f376afb05e85c0699523" @@ -4002,27 +3979,6 @@ "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" -"@wry/context@^0.6.0": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.6.1.tgz#c3c29c0ad622adb00f6a53303c4f965ee06ebeb2" - integrity sha512-LOmVnY1iTU2D8tv4Xf6MVMZZ+juIJ87Kt/plMijjN20NMAXGmH4u8bS1t0uT74cZ5gwpocYueV58YwyI8y+GKw== - dependencies: - tslib "^2.3.0" - -"@wry/equality@^0.5.0": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.5.2.tgz#72c8a7a7d884dff30b612f4f8464eba26c080e73" - integrity sha512-oVMxbUXL48EV/C0/M7gLVsoK6qRHPS85x8zECofEZOVvxGmIPLA9o5Z27cc2PoAyZz1S2VoM2A7FLAnpfGlneA== - dependencies: - tslib "^2.3.0" - -"@wry/trie@^0.3.0": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@wry/trie/-/trie-0.3.1.tgz#2279b790f15032f8bcea7fc944d27988e5b3b139" - integrity sha512-WwB53ikYudh9pIorgxrkHKrQZcCqNM/Q/bDzZBffEaGUKGuHrRb3zZUT9Sh2qw9yogC7SsdRmQ1ER0pqvd3bfw== - dependencies: - tslib "^2.3.0" - "@xmldom/xmldom@~0.7.0": version "0.7.5" resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.5.tgz#09fa51e356d07d0be200642b0e4f91d8e6dd408d" @@ -4472,6 +4428,11 @@ async@^2.4.0: dependencies: lodash "^4.17.14" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + at-least-node@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" @@ -5481,6 +5442,13 @@ colors@^1.1.2: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + command-exists@^1.2.8: version "1.2.9" resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" @@ -5758,6 +5726,13 @@ cross-fetch@^3.0.4: dependencies: node-fetch "2.6.1" +cross-fetch@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" + cross-spawn@7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -6182,6 +6157,11 @@ del@^4.1.1: pify "^4.0.1" rimraf "^2.6.3" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -6955,6 +6935,11 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extract-files@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a" + integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ== + fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -7274,6 +7259,15 @@ fork-ts-checker-webpack-plugin@4.1.6: tapable "^1.0.0" worker-rpc "^0.1.0" +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -7577,17 +7571,19 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -graphql-tag@^2.12.3: - version "2.12.6" - resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1" - integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg== +graphql-request@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-4.3.0.tgz#b934e08fcae764aa2cdc697d3c821f046cb5dbf2" + integrity sha512-2v6hQViJvSsifK606AliqiNiijb1uwWp6Re7o0RTyH+uRTv/u7Uqm2g4Fjq/LgZIzARB38RZEvVBFOQOVdlBow== dependencies: - tslib "^2.1.0" + cross-fetch "^3.1.5" + extract-files "^9.0.0" + form-data "^3.0.0" -graphql@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.2.0.tgz#de3150e80f1fc009590b92a9d16ab1b46e12b656" - integrity sha512-MuQd7XXrdOcmfwuLwC2jNvx0n3rxIuNYOxUtiee5XOmfrWo613ar2U8pE7aHAKh8VwfpifubpD9IP+EdEAEOsA== +graphql@^16.5.0: + version "16.5.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.5.0.tgz#41b5c1182eaac7f3d47164fb247f61e4dfb69c85" + integrity sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA== gzip-size@5.1.1: version "5.1.1" @@ -7604,6 +7600,11 @@ gzip-size@^6.0.0: dependencies: duplexer "^0.1.2" +hamt_plus@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/hamt_plus/-/hamt_plus-1.0.2.tgz#e21c252968c7e33b20f6a1b094cd85787a265601" + integrity sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA== + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -7732,7 +7733,7 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.0: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -8727,11 +8728,6 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -list@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/list/-/list-2.0.19.tgz#370a3d7d3e24cfd5ced2c89cda2baf28e31e2830" - integrity sha512-nnVaRp4RaMAQkCpypTThsdxKqgPMiSwJq93eAm2/IbpUa8sd04XKBhkKu+bMk63HmdjK8b8Cuh4xARHWX2ye/Q== - loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" @@ -9450,6 +9446,18 @@ mime-db@1.48.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mime-types@^2.1.26, mime-types@~2.1.24: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" @@ -9614,23 +9622,6 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mobx-react-lite@2: - version "2.0.7" - resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-2.0.7.tgz#1bfb3b4272668e288047cf0c7940b14e91cba284" - integrity sha512-YKAh2gThC6WooPnVZCoC+rV1bODAKFwkhxikzgH18wpBjkgTkkR9Sb0IesQAH5QrAEH/JQVmy47jcpQkf2Au3Q== - -mobx-react@6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-6.2.2.tgz#45e8e7c4894cac8399bba0a91060d7cfb8ea084b" - integrity sha512-Us6V4ng/iKIRJ8pWxdbdysC6bnS53ZKLKlVGBqzHx6J+gYPYbOotWvhHZnzh/W5mhpYXxlXif4kL2cxoWJOplQ== - dependencies: - mobx-react-lite "2" - -mobx@5.15.4: - version "5.15.4" - resolved "https://registry.yarnpkg.com/mobx/-/mobx-5.15.4.tgz#9da1a84e97ba624622f4e55a0bf3300fb931c2ab" - integrity sha512-xRFJxSU2Im3nrGCdjSuOTFmxVDGeqOHL+TyADCGbT0k4HHqGmx5u2yaHNryvoORpI4DfbzjJ5jPmuv+d7sioFw== - mockdate@^3.0.2: version "3.0.5" resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb" @@ -9928,6 +9919,13 @@ node-fetch@2.6.1: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -10282,14 +10280,6 @@ opener@^1.5.2: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== -optimism@^0.16.1: - version "0.16.1" - resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.16.1.tgz#7c8efc1f3179f18307b887e18c15c5b7133f6e7d" - integrity sha512-64i+Uw3otrndfq5kaoGNoY7pvOhSsjFEN4bdEFh80MWVk/dbgJfMv7VFDeCT8LxNAlEVhQmdVEbfE7X2nWNIIg== - dependencies: - "@wry/context" "^0.6.0" - "@wry/trie" "^0.3.0" - optimize-css-assets-webpack-plugin@^5.0.3: version "5.0.8" resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.8.tgz#cbccdcf5a6ef61d4f8cc78cf083a67446e5f402a" @@ -11739,6 +11729,13 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +recoil@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/recoil/-/recoil-0.7.4.tgz#d6508fa656d9c93e66fdf334e1f723a9e98801cf" + integrity sha512-sCXvQGMfSprkNU4ZRkJV4B0qFQSURJMgsICqY1952WRlg66NMwYqi6n67vhnhn0qw4zHU1gHXJuMvRDaiRNFZw== + dependencies: + hamt_plus "1.0.2" + recursive-readdir@2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" @@ -12967,11 +12964,6 @@ svgo@^1.0.0: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" - integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== - tailwindcss@>=2.0.0: version "3.0.24" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.24.tgz#22e31e801a44a78a1d9a81ecc52e13b69d85704d" @@ -13244,6 +13236,11 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + traverse@0.6.6: version "0.6.6" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" @@ -13254,13 +13251,6 @@ ts-interface-checker@^0.1.9: resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== -ts-invariant@^0.9.4: - version "0.9.4" - resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.9.4.tgz#42ac6c791aade267dd9dc65276549df5c5d71cac" - integrity sha512-63jtX/ZSwnUNi/WhXjnK8kz4cHHpYS60AnmA6ixz17l7E12a5puCWFlNpkne5Rl0J8TBPVHpGjsj4fxs8ObVLQ== - dependencies: - tslib "^2.1.0" - ts-pnp@^1.1.6: version "1.2.0" resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" @@ -13271,7 +13261,7 @@ tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== -tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0: +tslib@^2.0.1: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== @@ -13722,6 +13712,11 @@ web-vitals@0.2.1: resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-0.2.1.tgz#60782fa690243fe35613759a0c26431f57ba7b2d" integrity sha512-2pdRlp6gJpOCg0oMMqwFF0axjk5D9WInc09RSYtqFgPXQ15+YKNQ7YnBBEqAL5jvmfH9WvoXDMb8DHwux7pIew== +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -13794,6 +13789,14 @@ whatwg-fetch@3.0.0, whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + whatwg-url@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" @@ -14062,15 +14065,3 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zen-observable-ts@^1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.2.3.tgz#c2f5ccebe812faf0cfcde547e6004f65b1a6d769" - integrity sha512-hc/TGiPkAWpByykMwDcem3SdUgA4We+0Qb36bItSuJC9xD0XVBZoFHYoadAomDSNf64CG8Ydj0Qb8Od8BUWz5g== - dependencies: - zen-observable "0.8.15" - -zen-observable@0.8.15: - version "0.8.15" - resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" - integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==