Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e71aff8a9 | ||
|
|
a927d9783e | ||
|
|
4de28337a3 |
@@ -65,7 +65,7 @@ export default class API {
|
|||||||
public static readonly baseUrl =
|
public static readonly baseUrl =
|
||||||
process.env.NODE_ENV != 'development' && Platform.OS === 'web'
|
process.env.NODE_ENV != 'development' && Platform.OS === 'web'
|
||||||
? '/api'
|
? '/api'
|
||||||
: Constants.manifest?.extra?.apiUrl;
|
: "https://nightly.chroma.octohub.app/api";
|
||||||
public static async fetch(
|
public static async fetch(
|
||||||
params: FetchParams,
|
params: FetchParams,
|
||||||
handle: Pick<Required<HandleParams>, 'raw'>
|
handle: Pick<Required<HandleParams>, 'raw'>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import store, { persistor } from './state/Store';
|
import store, { persistor } from './state/Store';
|
||||||
import { Router } from './Navigation';
|
import { Router } from './Navigation';
|
||||||
import './i18n/i18n';
|
import './i18n/i18n';
|
||||||
@@ -10,12 +11,25 @@ import LanguageGate from './i18n/LanguageGate';
|
|||||||
import ThemeProvider, { ColorSchemeProvider } from './Theme';
|
import ThemeProvider, { ColorSchemeProvider } from './Theme';
|
||||||
import 'react-native-url-polyfill/auto';
|
import 'react-native-url-polyfill/auto';
|
||||||
import { QueryRules } from './Queries';
|
import { QueryRules } from './Queries';
|
||||||
|
import { useFonts } from 'expo-font';
|
||||||
|
|
||||||
const queryClient = new QueryClient(QueryRules);
|
const queryClient = new QueryClient(QueryRules);
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
// SplashScreen.preventAutoHideAsync();
|
||||||
|
// setTimeout(SplashScreen.hideAsync, 500);
|
||||||
|
|
||||||
SplashScreen.preventAutoHideAsync();
|
SplashScreen.preventAutoHideAsync();
|
||||||
setTimeout(SplashScreen.hideAsync, 500);
|
|
||||||
|
const [fontsLoaded] = useFonts({
|
||||||
|
'Lexend': require('./assets/fonts/Lexend-VariableFont_wght.ttf'),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (fontsLoaded) {
|
||||||
|
SplashScreen.hideAsync();
|
||||||
|
}
|
||||||
|
}, [fontsLoaded]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import { useDispatch } from 'react-redux';
|
|||||||
import { Translate, translate } from './i18n/i18n';
|
import { Translate, translate } from './i18n/i18n';
|
||||||
import SongLobbyView from './views/SongLobbyView';
|
import SongLobbyView from './views/SongLobbyView';
|
||||||
import AuthenticationView from './views/AuthenticationView';
|
import AuthenticationView from './views/AuthenticationView';
|
||||||
|
import SigninView from './views/SigninView';
|
||||||
|
import SignupView from './views/SignupView';
|
||||||
import StartPageView from './views/StartPageView';
|
import StartPageView from './views/StartPageView';
|
||||||
import HomeView from './views/HomeView';
|
import HomeView from './views/HomeView';
|
||||||
import SearchView from './views/SearchView';
|
import SearchView from './views/SearchView';
|
||||||
@@ -84,17 +86,27 @@ const publicRoutes = () =>
|
|||||||
link: '/',
|
link: '/',
|
||||||
},
|
},
|
||||||
Login: {
|
Login: {
|
||||||
component: (params: RouteProps<{}>) =>
|
component: SigninView,
|
||||||
AuthenticationView({ isSignup: false, ...params }),
|
options: { title: translate('signInBtn'), headerShown: false },
|
||||||
options: { title: translate('signInBtn') },
|
|
||||||
link: '/login',
|
link: '/login',
|
||||||
},
|
},
|
||||||
Signup: {
|
Signup: {
|
||||||
component: (params: RouteProps<{}>) =>
|
component: SignupView,
|
||||||
AuthenticationView({ isSignup: true, ...params }),
|
options: { title: translate('signUpBtn'), headerShown: false },
|
||||||
options: { title: translate('signUpBtn') },
|
|
||||||
link: '/signup',
|
link: '/signup',
|
||||||
},
|
},
|
||||||
|
// Login: {
|
||||||
|
// component: (params: RouteProps<{}>) =>
|
||||||
|
// AuthenticationView({ isSignup: false, ...params }),
|
||||||
|
// options: { title: translate('signInBtn') },
|
||||||
|
// link: '/login',
|
||||||
|
// },
|
||||||
|
// Signup: {
|
||||||
|
// component: (params: RouteProps<{}>) =>
|
||||||
|
// AuthenticationView({ isSignup: true, ...params }),
|
||||||
|
// options: { title: translate('signUpBtn') },
|
||||||
|
// link: '/signup',
|
||||||
|
// },
|
||||||
Oops: {
|
Oops: {
|
||||||
component: ProfileErrorView,
|
component: ProfileErrorView,
|
||||||
options: { title: 'Oops', headerShown: false },
|
options: { title: 'Oops', headerShown: false },
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ const ThemeProvider = ({ children }: { children: JSX.Element }) => {
|
|||||||
useSystemColorMode: false,
|
useSystemColorMode: false,
|
||||||
initialColorMode: colorScheme,
|
initialColorMode: colorScheme,
|
||||||
},
|
},
|
||||||
|
fonts: {
|
||||||
|
heading: "Lexend",
|
||||||
|
body: "Lexend",
|
||||||
|
mono: "Lexend",
|
||||||
|
},
|
||||||
colors: {
|
colors: {
|
||||||
primary: {
|
primary: {
|
||||||
50: '#e6faea',
|
50: '#e6faea',
|
||||||
|
|||||||
BIN
front/assets/banner.jpg
Normal file
BIN
front/assets/banner.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
front/assets/fonts/Lexend-VariableFont_wght.ttf
Normal file
BIN
front/assets/fonts/Lexend-VariableFont_wght.ttf
Normal file
Binary file not shown.
165
front/components/UI/ButtonBase.tsx
Normal file
165
front/components/UI/ButtonBase.tsx
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { StyleSheet, ActivityIndicator, View, Image } from 'react-native';
|
||||||
|
import Ionicons from '@expo/vector-icons/Ionicons';
|
||||||
|
import InteractiveBase from './InteractiveBase';
|
||||||
|
import { Text, useTheme } from 'native-base';
|
||||||
|
// import { BlurView } from 'expo-blur';
|
||||||
|
|
||||||
|
interface ButtonProps {
|
||||||
|
title?: string;
|
||||||
|
onPress?: () => Promise<any>;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
icon?: string;
|
||||||
|
iconImage?: string;
|
||||||
|
isCollapsed?: boolean;
|
||||||
|
type: 'filled' | 'outlined' | 'menu' | 'submenu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const ButtonBase: React.FC<ButtonProps> = ({ title, onPress, isDisabled, icon, iconImage, isCollapsed, type = 'filled'}) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const styleButton = StyleSheet.create({
|
||||||
|
Default: {
|
||||||
|
scale: 1,
|
||||||
|
shadowOpacity: 0.30,
|
||||||
|
shadowRadius: 4.65,
|
||||||
|
elevation: 8,
|
||||||
|
backgroundColor: colors.primary[400],
|
||||||
|
},
|
||||||
|
onHover: {
|
||||||
|
scale: 1.02,
|
||||||
|
shadowOpacity: 0.37,
|
||||||
|
shadowRadius: 7.49,
|
||||||
|
elevation: 12,
|
||||||
|
backgroundColor: colors.primary[500],
|
||||||
|
},
|
||||||
|
onPressed: {
|
||||||
|
scale: 0.98,
|
||||||
|
shadowOpacity: 0.23,
|
||||||
|
shadowRadius: 2.62,
|
||||||
|
elevation: 4,
|
||||||
|
backgroundColor: colors.primary[600],
|
||||||
|
},
|
||||||
|
Disabled: {
|
||||||
|
scale: 1,
|
||||||
|
shadowOpacity: 0.30,
|
||||||
|
shadowRadius: 4.65,
|
||||||
|
elevation: 8,
|
||||||
|
backgroundColor: colors.primary[400],
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const styleMenu = StyleSheet.create({
|
||||||
|
Default: {
|
||||||
|
scale: 1,
|
||||||
|
shadowOpacity: 0.30,
|
||||||
|
shadowRadius: 4.65,
|
||||||
|
elevation: 8,
|
||||||
|
backgroundColor: '#ff0000',
|
||||||
|
},
|
||||||
|
onHover: {
|
||||||
|
scale: 1.01,
|
||||||
|
shadowOpacity: 0.37,
|
||||||
|
shadowRadius: 7.49,
|
||||||
|
elevation: 12,
|
||||||
|
backgroundColor: '#0ff000',
|
||||||
|
},
|
||||||
|
onPressed: {
|
||||||
|
scale: 0.99,
|
||||||
|
shadowOpacity: 0.23,
|
||||||
|
shadowRadius: 2.62,
|
||||||
|
elevation: 4,
|
||||||
|
backgroundColor: '#000',
|
||||||
|
},
|
||||||
|
Disabled: {
|
||||||
|
scale: 1,
|
||||||
|
shadowOpacity: 0.30,
|
||||||
|
shadowRadius: 4.65,
|
||||||
|
elevation: 8,
|
||||||
|
backgroundColor: '#0000',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const typeToStyleAnimator = {'filled': styleButton,'outlined': styleButton,'menu': styleMenu,'submenu': styleButton};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InteractiveBase
|
||||||
|
style={styles.container}
|
||||||
|
styleAnimate={typeToStyleAnimator[type]}
|
||||||
|
onPress={async () => {
|
||||||
|
if (onPress && !isDisabled) {
|
||||||
|
setLoading(true);
|
||||||
|
await onPress();
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
isOutlined={type === 'outlined'}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<ActivityIndicator size="small" color={type === 'outlined' ? '#6075F9' : '#FFFFFF'} />
|
||||||
|
) : (
|
||||||
|
<View style={styles.content}>
|
||||||
|
{icon && <Ionicons name={icon} size={18} color={type === 'outlined' ? '#ff0000' : '#FFFFFF'} />}
|
||||||
|
{iconImage && <Image source={{uri: iconImage}} style={styles.icon} />}
|
||||||
|
{title && <Text style={styles.text}>{title}</Text>}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</InteractiveBase>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styleAnimate = StyleSheet.create({
|
||||||
|
Default: {
|
||||||
|
scale: 1,
|
||||||
|
shadowOpacity: 0.30,
|
||||||
|
shadowRadius: 4.65,
|
||||||
|
elevation: 8,
|
||||||
|
backgroundColor: '#00ff00',
|
||||||
|
},
|
||||||
|
onHover: {
|
||||||
|
scale: 1.01,
|
||||||
|
shadowOpacity: 0.37,
|
||||||
|
shadowRadius: 7.49,
|
||||||
|
elevation: 12,
|
||||||
|
backgroundColor: '#0000ff',
|
||||||
|
},
|
||||||
|
onPressed: {
|
||||||
|
scale: 0.99,
|
||||||
|
shadowOpacity: 0.23,
|
||||||
|
shadowRadius: 2.62,
|
||||||
|
elevation: 4,
|
||||||
|
backgroundColor: '#ff0000',
|
||||||
|
},
|
||||||
|
Disabled: {
|
||||||
|
scale: 1,
|
||||||
|
shadowOpacity: 0.30,
|
||||||
|
shadowRadius: 4.65,
|
||||||
|
elevation: 8,
|
||||||
|
backgroundColor: '#000000',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
padding: 10,
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
// marginRight: 8,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: '#fff',
|
||||||
|
marginHorizontal: 8
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ButtonBase;
|
||||||
242
front/components/UI/InteractiveBase.tsx
Normal file
242
front/components/UI/InteractiveBase.tsx
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
import React, { useRef, useState } from 'react';
|
||||||
|
import { Animated, StyleSheet, TouchableOpacity, ActivityIndicator, View, Image, StyleProp, ViewStyle } from 'react-native';
|
||||||
|
// import Ionicons from '@expo/vector-icons/Ionicons';
|
||||||
|
// import { Text, useTheme } from 'native-base'
|
||||||
|
// import { BlurView } from '@react-native-community/blur';
|
||||||
|
|
||||||
|
interface InteractiveBaseProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
onPress?: () => Promise<any>;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
isOutlined?: boolean;
|
||||||
|
style?: StyleProp<ViewStyle>,
|
||||||
|
styleAnimate: {
|
||||||
|
Default: {
|
||||||
|
scale: number,
|
||||||
|
shadowOpacity: number,
|
||||||
|
shadowRadius: number,
|
||||||
|
elevation: number,
|
||||||
|
backgroundColor: string,
|
||||||
|
},
|
||||||
|
onHover: {
|
||||||
|
scale: number,
|
||||||
|
shadowOpacity: number,
|
||||||
|
shadowRadius: number,
|
||||||
|
elevation: number,
|
||||||
|
backgroundColor: string,
|
||||||
|
},
|
||||||
|
onPressed: {
|
||||||
|
scale: number,
|
||||||
|
shadowOpacity: number,
|
||||||
|
shadowRadius: number,
|
||||||
|
elevation: number,
|
||||||
|
backgroundColor: string,
|
||||||
|
},
|
||||||
|
Disabled: {
|
||||||
|
scale: number,
|
||||||
|
shadowOpacity: number,
|
||||||
|
shadowRadius: number,
|
||||||
|
elevation: number,
|
||||||
|
backgroundColor: string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const InteractiveBase: React.FC<InteractiveBaseProps> = ({ children, onPress, style, styleAnimate, isDisabled = false, isOutlined = false }) => {
|
||||||
|
const scaleAnimator = useRef(new Animated.Value(1)).current;
|
||||||
|
const scaleValue = scaleAnimator.interpolate({
|
||||||
|
inputRange: [0, 1, 2],
|
||||||
|
outputRange: [styleAnimate.Default.scale, styleAnimate.onHover.scale, styleAnimate.onPressed.scale],
|
||||||
|
});
|
||||||
|
const shadowOpacityAnimator = useRef(new Animated.Value(0)).current;
|
||||||
|
const shadowOpacityValue = shadowOpacityAnimator.interpolate({
|
||||||
|
inputRange: [0, 1, 2],
|
||||||
|
outputRange: [styleAnimate.Default.shadowOpacity, styleAnimate.onHover.shadowOpacity, styleAnimate.onPressed.shadowOpacity],
|
||||||
|
});
|
||||||
|
const shadowRadiusAnimator = useRef(new Animated.Value(0)).current;
|
||||||
|
const shadowRadiusValue = shadowRadiusAnimator.interpolate({
|
||||||
|
inputRange: [0, 1, 2],
|
||||||
|
outputRange: [styleAnimate.Default.shadowRadius, styleAnimate.onHover.shadowRadius, styleAnimate.onPressed.shadowRadius],
|
||||||
|
});
|
||||||
|
const elevationAnimator = useRef(new Animated.Value(0)).current;
|
||||||
|
const elevationValue = elevationAnimator.interpolate({
|
||||||
|
inputRange: [0, 1, 2],
|
||||||
|
outputRange: [styleAnimate.Default.elevation, styleAnimate.onHover.elevation, styleAnimate.onPressed.elevation],
|
||||||
|
});
|
||||||
|
const backgroundColorAnimator = useRef(new Animated.Value(0)).current;
|
||||||
|
const backgroundColorValue = backgroundColorAnimator.interpolate({
|
||||||
|
inputRange: [0, 1, 2],
|
||||||
|
outputRange: [styleAnimate.Default.backgroundColor, styleAnimate.onHover.backgroundColor, styleAnimate.onPressed.backgroundColor],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mouse Enter
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
Animated.parallel([
|
||||||
|
Animated.spring(scaleAnimator, {
|
||||||
|
toValue: 1,
|
||||||
|
useNativeDriver: true,
|
||||||
|
}),
|
||||||
|
Animated.timing(backgroundColorAnimator, {
|
||||||
|
toValue: 1,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
Animated.timing(shadowRadiusAnimator, {
|
||||||
|
toValue: 1,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
Animated.timing(shadowOpacityAnimator, {
|
||||||
|
toValue: 1,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
Animated.timing(elevationAnimator, {
|
||||||
|
toValue: 1,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
]).start();
|
||||||
|
}
|
||||||
|
// Mouse Down
|
||||||
|
const handlePressIn = () => {
|
||||||
|
Animated.parallel([
|
||||||
|
Animated.spring(scaleAnimator, {
|
||||||
|
toValue: 2,
|
||||||
|
useNativeDriver: true,
|
||||||
|
}),
|
||||||
|
Animated.timing(backgroundColorAnimator, {
|
||||||
|
toValue: 2,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
Animated.timing(shadowRadiusAnimator, {
|
||||||
|
toValue: 2,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
Animated.timing(shadowOpacityAnimator, {
|
||||||
|
toValue: 2,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
Animated.timing(elevationAnimator, {
|
||||||
|
toValue: 2,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
]).start();
|
||||||
|
};
|
||||||
|
// Mouse Up
|
||||||
|
const handlePressOut = async () => {
|
||||||
|
Animated.parallel([
|
||||||
|
Animated.spring(scaleAnimator, {
|
||||||
|
toValue: 1,
|
||||||
|
useNativeDriver: true,
|
||||||
|
}),
|
||||||
|
Animated.timing(backgroundColorAnimator, {
|
||||||
|
toValue: 1,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
Animated.timing(shadowRadiusAnimator, {
|
||||||
|
toValue: 1,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
Animated.timing(shadowOpacityAnimator, {
|
||||||
|
toValue: 1,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
Animated.timing(elevationAnimator, {
|
||||||
|
toValue: 1,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
]).start();
|
||||||
|
|
||||||
|
if (onPress && !isDisabled) {
|
||||||
|
await onPress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Mouse Leave
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
Animated.parallel([
|
||||||
|
Animated.spring(scaleAnimator, {
|
||||||
|
toValue: 0,
|
||||||
|
useNativeDriver: true,
|
||||||
|
}),
|
||||||
|
Animated.timing(backgroundColorAnimator, {
|
||||||
|
toValue: 0,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
Animated.timing(shadowRadiusAnimator, {
|
||||||
|
toValue: 0,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
Animated.timing(shadowOpacityAnimator, {
|
||||||
|
toValue: 0,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
Animated.timing(elevationAnimator, {
|
||||||
|
toValue: 0,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: true,
|
||||||
|
}),
|
||||||
|
]).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
style,
|
||||||
|
isDisabled ? styleAnimate.Disabled : {
|
||||||
|
backgroundColor: isOutlined ? 'rgba(0,0,0,0.3)' : backgroundColorValue,
|
||||||
|
borderColor: isOutlined ? backgroundColorValue : 'transparent',
|
||||||
|
borderWidth: 2,
|
||||||
|
transform: [{ scale: scaleValue }],
|
||||||
|
shadowOpacity: shadowOpacityValue,
|
||||||
|
shadowRadius: shadowRadiusValue,
|
||||||
|
elevation: elevationValue,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<TouchableOpacity
|
||||||
|
activeOpacity={1}
|
||||||
|
disabled={isDisabled}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onPressIn={handlePressIn}
|
||||||
|
onPressOut={handlePressOut}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
style={styles.container}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{/* <BlurView
|
||||||
|
style={{
|
||||||
|
width: '420px',
|
||||||
|
height: '50px',
|
||||||
|
borderRadius: 20,
|
||||||
|
borderWidth: 1,
|
||||||
|
|
||||||
|
}}
|
||||||
|
blurType="light"
|
||||||
|
blurAmount={20}
|
||||||
|
blurRadius={5}
|
||||||
|
/> */}
|
||||||
|
</TouchableOpacity>
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default InteractiveBase;
|
||||||
23
front/components/UI/LinkBase.tsx
Normal file
23
front/components/UI/LinkBase.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React, { ReactNode, FunctionComponent } from 'react';
|
||||||
|
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
linkText: {
|
||||||
|
textDecorationLine: 'underline',
|
||||||
|
color: '#A3AFFC',
|
||||||
|
fontWeight: '700',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface LinkBaseProps {
|
||||||
|
children: ReactNode;
|
||||||
|
onPress: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LinkBase: FunctionComponent<LinkBaseProps> = ({ children, onPress }) => (
|
||||||
|
<TouchableOpacity onPress={onPress}>
|
||||||
|
<Text style={styles.linkText}>{children}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default LinkBase;
|
||||||
34
front/components/UI/SeparatorBase.tsx
Normal file
34
front/components/UI/SeparatorBase.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import React, { FunctionComponent, ReactNode } from 'react';
|
||||||
|
import { View, Text, StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
line: {
|
||||||
|
flex: 1,
|
||||||
|
height: 2,
|
||||||
|
backgroundColor: 'white',
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
width: '100%',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginVertical: 20,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: 'white',
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface SeparatorBaseProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SeparatorBase: FunctionComponent<SeparatorBaseProps> = ({children}) => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.line} />
|
||||||
|
<Text style={styles.text}>{children}</Text>
|
||||||
|
<View style={styles.line} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default SeparatorBase;
|
||||||
98
front/components/UI/SettingsBase.tsx
Normal file
98
front/components/UI/SettingsBase.tsx
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { StyleSheet, ActivityIndicator, View, Image } from 'react-native';
|
||||||
|
import Ionicons from '@expo/vector-icons/Ionicons';
|
||||||
|
import InteractiveBase from './InteractiveBase';
|
||||||
|
import { Text, useTheme } from 'native-base';
|
||||||
|
// import { BlurView } from 'expo-blur';
|
||||||
|
|
||||||
|
interface SettingProps {
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
onPress?: () => Promise<any>;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingBase: React.FC<SettingProps> = ({ title, description, onPress, icon, children}) => {
|
||||||
|
const styleSetting = StyleSheet.create({
|
||||||
|
Default: {
|
||||||
|
scale: 1,
|
||||||
|
shadowOpacity: 0.30,
|
||||||
|
shadowRadius: 4.65,
|
||||||
|
elevation: 8,
|
||||||
|
backgroundColor: 'rgba(16, 16, 20, 0.50)',
|
||||||
|
},
|
||||||
|
onHover: {
|
||||||
|
scale: 1,
|
||||||
|
shadowOpacity: 0.30,
|
||||||
|
shadowRadius: 4.65,
|
||||||
|
elevation: 8,
|
||||||
|
backgroundColor: 'rgba(32, 32, 40, 0.50)',
|
||||||
|
},
|
||||||
|
onPressed: {
|
||||||
|
scale: 1,
|
||||||
|
shadowOpacity: 0.30,
|
||||||
|
shadowRadius: 4.65,
|
||||||
|
elevation: 8,
|
||||||
|
backgroundColor: 'rgba(16, 16, 20, 0.50)',
|
||||||
|
},
|
||||||
|
Disabled: {
|
||||||
|
scale: 1,
|
||||||
|
shadowOpacity: 0.30,
|
||||||
|
shadowRadius: 4.65,
|
||||||
|
elevation: 8,
|
||||||
|
backgroundColor: 'rgba(16, 16, 20, 0.50)',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InteractiveBase
|
||||||
|
style={[styles.container, {width: '100%'}]}
|
||||||
|
styleAnimate={styleSetting}
|
||||||
|
onPress={async () => {
|
||||||
|
if (onPress) {
|
||||||
|
await onPress();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View style={styles.content}>
|
||||||
|
<Ionicons name={icon} size={24} color="#fff"/>
|
||||||
|
<View style={styles.info}>
|
||||||
|
<Text style={styles.text}>{title}</Text>
|
||||||
|
<Text style={styles.description}>{description}</Text>
|
||||||
|
</View>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
</InteractiveBase>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: 'rgba(16, 16, 20, 0.50)',
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
alignSelf: 'stretch',
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
marginHorizontal: 16,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default SettingBase;
|
||||||
109
front/components/UI/TextFieldBase.tsx
Normal file
109
front/components/UI/TextFieldBase.tsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { View, TextInput, TouchableOpacity, StyleSheet } from 'react-native';
|
||||||
|
import Icon from 'react-native-vector-icons/Ionicons'; // Supposons que nous utilisons la bibliothèque Ionicons pour les icônes
|
||||||
|
|
||||||
|
export interface TextFieldBaseProps {
|
||||||
|
value?: string;
|
||||||
|
icon?: string;
|
||||||
|
iconColor?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
autoComplete?:
|
||||||
|
| 'birthdate-day'
|
||||||
|
| 'birthdate-full'
|
||||||
|
| 'birthdate-month'
|
||||||
|
| 'birthdate-year'
|
||||||
|
| 'cc-csc'
|
||||||
|
| 'cc-exp'
|
||||||
|
| 'cc-exp-day'
|
||||||
|
| 'cc-exp-month'
|
||||||
|
| 'cc-exp-year'
|
||||||
|
| 'cc-number'
|
||||||
|
| 'email'
|
||||||
|
| 'gender'
|
||||||
|
| 'name'
|
||||||
|
| 'name-family'
|
||||||
|
| 'name-given'
|
||||||
|
| 'name-middle'
|
||||||
|
| 'name-middle-initial'
|
||||||
|
| 'name-prefix'
|
||||||
|
| 'name-suffix'
|
||||||
|
| 'password'
|
||||||
|
| 'password-new'
|
||||||
|
| 'postal-address'
|
||||||
|
| 'postal-address-country'
|
||||||
|
| 'postal-address-extended'
|
||||||
|
| 'postal-address-extended-postal-code'
|
||||||
|
| 'postal-address-locality'
|
||||||
|
| 'postal-address-region'
|
||||||
|
| 'postal-code'
|
||||||
|
| 'street-address'
|
||||||
|
| 'sms-otp'
|
||||||
|
| 'tel'
|
||||||
|
| 'tel-country-code'
|
||||||
|
| 'tel-national'
|
||||||
|
| 'tel-device'
|
||||||
|
| 'username'
|
||||||
|
| 'username-new'
|
||||||
|
| 'off'
|
||||||
|
| undefined;
|
||||||
|
isSecret?: boolean;
|
||||||
|
isRequired?: boolean;
|
||||||
|
onChangeText?: ((text: string) => void) | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextFieldBase: React.FC<TextFieldBaseProps> = ({ placeholder = '', icon, iconColor, autoComplete = 'off', isSecret = false, isRequired = false, ...props }) => {
|
||||||
|
const [isPasswordVisible, setPasswordVisible] = useState(!isSecret);
|
||||||
|
const [isFocused, setFocused] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.iconContainerLeft}>
|
||||||
|
{icon && <Icon name={icon} size={24} color={iconColor ? iconColor : (isFocused ? '#6075F9' : '#454562')} />}
|
||||||
|
</View>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
autoComplete={autoComplete}
|
||||||
|
placeholder={placeholder + (isRequired ? '*' : '')}
|
||||||
|
placeholderTextColor='#454562'
|
||||||
|
secureTextEntry={isSecret ? !isPasswordVisible : false}
|
||||||
|
onFocus={() => setFocused(true)}
|
||||||
|
onBlur={() => setFocused(false)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
{isSecret && (
|
||||||
|
<TouchableOpacity style={styles.iconContainerRight} onPress={() => setPasswordVisible(prevState => !prevState)}>
|
||||||
|
<Icon name={isPasswordVisible ? 'eye-off' : 'eye'} size={24} color='#454562' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#22222D',
|
||||||
|
borderRadius: 16,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
flex: 1,
|
||||||
|
color: '#ffffff',
|
||||||
|
paddingHorizontal: 16 + 24 + 16,
|
||||||
|
paddingVertical: 16,
|
||||||
|
outlineStyle: 'none',
|
||||||
|
},
|
||||||
|
iconContainerLeft: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 16,
|
||||||
|
zIndex: 1,
|
||||||
|
},
|
||||||
|
iconContainerRight: {
|
||||||
|
position: 'absolute',
|
||||||
|
outlineStyle: 'none',
|
||||||
|
right: 16,
|
||||||
|
zIndex: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TextFieldBase;
|
||||||
183
front/components/UI/TextFormField copy.tsx
Normal file
183
front/components/UI/TextFormField copy.tsx
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { View, TextInput, TouchableOpacity, StyleSheet, Text, Animated } from 'react-native';
|
||||||
|
import Icon from 'react-native-vector-icons/Ionicons'; // Supposons que nous utilisons la bibliothèque Ionicons pour les icônes
|
||||||
|
|
||||||
|
interface TextFormFieldProps {
|
||||||
|
value?: string;
|
||||||
|
icon?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
autoComplete?:
|
||||||
|
| 'birthdate-day'
|
||||||
|
| 'birthdate-full'
|
||||||
|
| 'birthdate-month'
|
||||||
|
| 'birthdate-year'
|
||||||
|
| 'cc-csc'
|
||||||
|
| 'cc-exp'
|
||||||
|
| 'cc-exp-day'
|
||||||
|
| 'cc-exp-month'
|
||||||
|
| 'cc-exp-year'
|
||||||
|
| 'cc-number'
|
||||||
|
| 'email'
|
||||||
|
| 'gender'
|
||||||
|
| 'name'
|
||||||
|
| 'name-family'
|
||||||
|
| 'name-given'
|
||||||
|
| 'name-middle'
|
||||||
|
| 'name-middle-initial'
|
||||||
|
| 'name-prefix'
|
||||||
|
| 'name-suffix'
|
||||||
|
| 'password'
|
||||||
|
| 'password-new'
|
||||||
|
| 'postal-address'
|
||||||
|
| 'postal-address-country'
|
||||||
|
| 'postal-address-extended'
|
||||||
|
| 'postal-address-extended-postal-code'
|
||||||
|
| 'postal-address-locality'
|
||||||
|
| 'postal-address-region'
|
||||||
|
| 'postal-code'
|
||||||
|
| 'street-address'
|
||||||
|
| 'sms-otp'
|
||||||
|
| 'tel'
|
||||||
|
| 'tel-country-code'
|
||||||
|
| 'tel-national'
|
||||||
|
| 'tel-device'
|
||||||
|
| 'username'
|
||||||
|
| 'username-new'
|
||||||
|
| 'off'
|
||||||
|
| undefined;
|
||||||
|
isSecret?: boolean;
|
||||||
|
isRequired?: boolean;
|
||||||
|
error?: string;
|
||||||
|
onChangeText?: ((text: string) => void) | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ERROR_HEIGHT = 20;
|
||||||
|
const ERROR_PADDING_TOP = 8;
|
||||||
|
|
||||||
|
const TextFormField: React.FC<TextFormFieldProps> = ({ value = '', placeholder = '', icon, autoComplete = 'off', isSecret = false, isRequired = false, error, ...props }) => {
|
||||||
|
const [isPasswordVisible, setPasswordVisible] = useState(!isSecret);
|
||||||
|
const [isFocused, setFocused] = useState(false);
|
||||||
|
const [fieldValue, setFieldValue] = useState(value);
|
||||||
|
const fadeAnim = React.useRef(new Animated.Value(0)).current; // Initial value for opacity: 0
|
||||||
|
const heightAnim = React.useRef(new Animated.Value(0)).current; // Initial value for height: 0
|
||||||
|
const paddingTopAnim = React.useRef(new Animated.Value(0)).current; // Initial value for paddingTop: 0
|
||||||
|
|
||||||
|
// Update fieldValue whenever value changes
|
||||||
|
useEffect(() => {
|
||||||
|
setFieldValue(value);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
// Animate the error message
|
||||||
|
useEffect(() => {
|
||||||
|
Animated.parallel([
|
||||||
|
Animated.timing(
|
||||||
|
fadeAnim,
|
||||||
|
{
|
||||||
|
toValue: error ? 1 : 0,
|
||||||
|
duration: 500,
|
||||||
|
useNativeDriver: true
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Animated.timing(
|
||||||
|
heightAnim,
|
||||||
|
{
|
||||||
|
toValue: error ? ERROR_HEIGHT : 0,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false // height cannot be animated using native driver
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Animated.timing(
|
||||||
|
paddingTopAnim,
|
||||||
|
{
|
||||||
|
toValue: error ? ERROR_PADDING_TOP : 0,
|
||||||
|
duration: 150,
|
||||||
|
useNativeDriver: false // paddingTop cannot be animated using native driver
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]).start();
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
const handleTextChange = (text: string) => {
|
||||||
|
setFieldValue(text);
|
||||||
|
if (props.onChangeText) {
|
||||||
|
props.onChangeText(text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.wrapper}>
|
||||||
|
<View style={[styles.container, error && styles.error, isFocused && styles.containerFocused]}>
|
||||||
|
<View style={styles.iconContainerLeft}>
|
||||||
|
{icon && <Icon name={icon} size={24} color={error ? 'red' : (isFocused ? '#6075F9' : '#454562')} />}
|
||||||
|
</View>
|
||||||
|
<TextInput
|
||||||
|
value={fieldValue}
|
||||||
|
style={styles.input}
|
||||||
|
autoComplete={autoComplete}
|
||||||
|
placeholder={placeholder + (isRequired ? '*' : '')}
|
||||||
|
placeholderTextColor='#454562'
|
||||||
|
secureTextEntry={isSecret ? !isPasswordVisible : false}
|
||||||
|
onFocus={() => setFocused(true)}
|
||||||
|
onBlur={() => setFocused(false)}
|
||||||
|
onChangeText={handleTextChange}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
{isSecret && (
|
||||||
|
<TouchableOpacity style={styles.iconContainerRight} onPress={() => setPasswordVisible(prevState => !prevState)}>
|
||||||
|
<Icon name={isPasswordVisible ? 'eye-off' : 'eye'} size={24} color='#454562' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<Animated.View style={{...styles.errorContainer, opacity: fadeAnim, height: heightAnim, paddingTop: paddingTopAnim}}>
|
||||||
|
<Icon name="warning" size={16} color='red' />
|
||||||
|
<Text style={styles.errorText}>{error}</Text>
|
||||||
|
</Animated.View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
wrapper: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#22222D',
|
||||||
|
borderRadius: 16,
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
},
|
||||||
|
containerFocused: {
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
flex: 1,
|
||||||
|
color: '#ffffff',
|
||||||
|
paddingHorizontal: 16 + 24 + 16,
|
||||||
|
paddingVertical: 16,
|
||||||
|
outlineStyle: 'none',
|
||||||
|
},
|
||||||
|
iconContainerLeft: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 16,
|
||||||
|
zIndex: 1,
|
||||||
|
},
|
||||||
|
iconContainerRight: {
|
||||||
|
position: 'absolute',
|
||||||
|
outlineStyle: 'none',
|
||||||
|
right: 16,
|
||||||
|
zIndex: 1,
|
||||||
|
},
|
||||||
|
errorContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingLeft: 40,
|
||||||
|
},
|
||||||
|
errorText: {
|
||||||
|
color: 'red',
|
||||||
|
fontSize: 12,
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TextFormField;
|
||||||
77
front/components/UI/TextFormField.tsx
Normal file
77
front/components/UI/TextFormField.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { View, Text, Animated, StyleSheet } from 'react-native';
|
||||||
|
import Icon from 'react-native-vector-icons/Ionicons';
|
||||||
|
import TextFieldBase, { TextFieldBaseProps } from './TextFieldBase';
|
||||||
|
|
||||||
|
interface TextFormFieldProps extends TextFieldBaseProps {
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ERROR_HEIGHT = 20;
|
||||||
|
const ERROR_PADDING_TOP = 8;
|
||||||
|
|
||||||
|
const TextFormField: React.FC<TextFormFieldProps> = ({ error, ...textFieldBaseProps }) => {
|
||||||
|
const fadeAnim = React.useRef(new Animated.Value(0)).current;
|
||||||
|
const heightAnim = React.useRef(new Animated.Value(0)).current;
|
||||||
|
const paddingTopAnim = React.useRef(new Animated.Value(0)).current;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Animated.parallel([
|
||||||
|
Animated.timing(
|
||||||
|
fadeAnim,
|
||||||
|
{
|
||||||
|
toValue: error ? 1 : 0,
|
||||||
|
duration: 150,
|
||||||
|
useNativeDriver: true
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Animated.timing(
|
||||||
|
heightAnim,
|
||||||
|
{
|
||||||
|
toValue: error ? ERROR_HEIGHT : 0,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Animated.timing(
|
||||||
|
paddingTopAnim,
|
||||||
|
{
|
||||||
|
toValue: error ? ERROR_PADDING_TOP : 0,
|
||||||
|
duration: 250,
|
||||||
|
useNativeDriver: false
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]).start();
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.wrapper}>
|
||||||
|
<TextFieldBase
|
||||||
|
iconColor={error ? '#f7253d' : undefined}
|
||||||
|
{...textFieldBaseProps}
|
||||||
|
/>
|
||||||
|
<Animated.View style={{...styles.errorContainer, opacity: fadeAnim, height: heightAnim, paddingTop: paddingTopAnim}}>
|
||||||
|
<Icon name="alert-circle" size={16} color='#f7253d' />
|
||||||
|
<Text style={styles.errorText}>{error}</Text>
|
||||||
|
</Animated.View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
wrapper: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
errorContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingLeft: 16,
|
||||||
|
},
|
||||||
|
errorText: {
|
||||||
|
color: '#f7253d',
|
||||||
|
fontSize: 12,
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TextFormField;
|
||||||
@@ -5,6 +5,8 @@ import { Translate, translate } from '../../i18n/i18n';
|
|||||||
import { string } from 'yup';
|
import { string } from 'yup';
|
||||||
import { FormControl, Input, Stack, WarningOutlineIcon, Box, useToast } from 'native-base';
|
import { FormControl, Input, Stack, WarningOutlineIcon, Box, useToast } from 'native-base';
|
||||||
import TextButton from '../TextButton';
|
import TextButton from '../TextButton';
|
||||||
|
import TextFormField from '../UI/TextFormField';
|
||||||
|
import ButtonBase from '../UI/ButtonBase';
|
||||||
|
|
||||||
interface SigninFormProps {
|
interface SigninFormProps {
|
||||||
onSubmit: (username: string, password: string) => Promise<string>;
|
onSubmit: (username: string, password: string) => Promise<string>;
|
||||||
@@ -21,8 +23,6 @@ const LoginForm = ({ onSubmit }: SigninFormProps) => {
|
|||||||
error: null as string | null,
|
error: null as string | null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const [submittingForm, setSubmittingForm] = React.useState(false);
|
|
||||||
|
|
||||||
const validationSchemas = {
|
const validationSchemas = {
|
||||||
username: string()
|
username: string()
|
||||||
.min(3, translate('usernameTooShort'))
|
.min(3, translate('usernameTooShort'))
|
||||||
@@ -35,18 +35,15 @@ const LoginForm = ({ onSubmit }: SigninFormProps) => {
|
|||||||
};
|
};
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
return (
|
return (
|
||||||
<Box alignItems="center" style={{ width: '100%' }}>
|
<Box alignItems="center" style={{ width: '100%', backgroundColor: "#101014" }}>
|
||||||
<Stack mx="4" style={{ width: '80%', maxWidth: 400 }}>
|
<Stack mx="4" style={{ width: '80%', maxWidth: 400 }}>
|
||||||
<FormControl
|
<FormControl
|
||||||
isRequired
|
isRequired
|
||||||
isInvalid={formData.username.error !== null || formData.password.error !== null}
|
isInvalid={formData.username.error !== null || formData.password.error !== null}
|
||||||
>
|
>
|
||||||
<FormControl.Label>
|
<TextFormField
|
||||||
<Translate translationKey="username" />
|
error={formData.username.error}
|
||||||
</FormControl.Label>
|
icon='person'
|
||||||
<Input
|
|
||||||
isRequired
|
|
||||||
type="text"
|
|
||||||
placeholder="Username"
|
placeholder="Username"
|
||||||
autoComplete="username"
|
autoComplete="username"
|
||||||
value={formData.username.value}
|
value={formData.username.value}
|
||||||
@@ -59,16 +56,14 @@ const LoginForm = ({ onSubmit }: SigninFormProps) => {
|
|||||||
setFormData({ ...formData, username: { value: t, error } });
|
setFormData({ ...formData, username: { value: t, error } });
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
|
||||||
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
|
||||||
{formData.username.error}
|
|
||||||
</FormControl.ErrorMessage>
|
|
||||||
<FormControl.Label>
|
|
||||||
<Translate translationKey="password" />
|
|
||||||
</FormControl.Label>
|
|
||||||
<Input
|
|
||||||
isRequired
|
isRequired
|
||||||
type="password"
|
/>
|
||||||
|
<TextFormField
|
||||||
|
isRequired
|
||||||
|
isSecret
|
||||||
|
error={formData.password.error}
|
||||||
|
icon='lock-closed'
|
||||||
|
placeholder="Password"
|
||||||
autoComplete="password"
|
autoComplete="password"
|
||||||
value={formData.password.value}
|
value={formData.password.value}
|
||||||
onChangeText={(t) => {
|
onChangeText={(t) => {
|
||||||
@@ -81,13 +76,8 @@ const LoginForm = ({ onSubmit }: SigninFormProps) => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
<ButtonBase
|
||||||
{formData.password.error}
|
title="Signin"
|
||||||
</FormControl.ErrorMessage>
|
|
||||||
<TextButton
|
|
||||||
translate={{ translationKey: 'signInBtn' }}
|
|
||||||
style={{ marginTop: 10 }}
|
|
||||||
isLoading={submittingForm}
|
|
||||||
isDisabled={
|
isDisabled={
|
||||||
formData.password.error !== null ||
|
formData.password.error !== null ||
|
||||||
formData.username.error !== null ||
|
formData.username.error !== null ||
|
||||||
@@ -95,24 +85,27 @@ const LoginForm = ({ onSubmit }: SigninFormProps) => {
|
|||||||
formData.password.value === ''
|
formData.password.value === ''
|
||||||
}
|
}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
setSubmittingForm(true);
|
|
||||||
try {
|
try {
|
||||||
const resp = await onSubmit(
|
const resp = await onSubmit(
|
||||||
formData.username.value,
|
formData.username.value,
|
||||||
formData.password.value
|
formData.password.value
|
||||||
);
|
);
|
||||||
toast.show({ description: resp, colorScheme: 'secondary' });
|
toast.show({ description: resp, colorScheme: 'secondary' });
|
||||||
setSubmittingForm(false);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.show({
|
toast.show({
|
||||||
description: e as string,
|
description: e as string,
|
||||||
colorScheme: 'red',
|
colorScheme: 'red',
|
||||||
avoidKeyboard: true,
|
avoidKeyboard: true,
|
||||||
});
|
});
|
||||||
setSubmittingForm(false);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<ButtonBase
|
||||||
|
// icon='logo-google'
|
||||||
|
isOutlined
|
||||||
|
iconImage='https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/2008px-Google_%22G%22_Logo.svg.png'
|
||||||
|
title="Signin with google"
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"@expo/vector-icons": "^13.0.0",
|
"@expo/vector-icons": "^13.0.0",
|
||||||
"@motiz88/react-native-midi": "^0.0.5",
|
"@motiz88/react-native-midi": "^0.0.5",
|
||||||
"@react-native-async-storage/async-storage": "~1.17.3",
|
"@react-native-async-storage/async-storage": "~1.17.3",
|
||||||
|
"@react-native-community/blur": "^4.3.2",
|
||||||
"@react-navigation/native": "^6.0.11",
|
"@react-navigation/native": "^6.0.11",
|
||||||
"@react-navigation/native-stack": "^6.7.0",
|
"@react-navigation/native-stack": "^6.7.0",
|
||||||
"@reduxjs/toolkit": "^1.8.3",
|
"@reduxjs/toolkit": "^1.8.3",
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
"expo": "^47.0.8",
|
"expo": "^47.0.8",
|
||||||
"expo-asset": "~8.7.0",
|
"expo-asset": "~8.7.0",
|
||||||
"expo-dev-client": "~2.0.1",
|
"expo-dev-client": "~2.0.1",
|
||||||
|
"expo-linear-gradient": "~12.0.1",
|
||||||
"expo-linking": "~3.3.1",
|
"expo-linking": "~3.3.1",
|
||||||
"expo-screen-orientation": "~5.0.1",
|
"expo-screen-orientation": "~5.0.1",
|
||||||
"expo-secure-store": "~12.0.0",
|
"expo-secure-store": "~12.0.0",
|
||||||
@@ -52,6 +54,7 @@
|
|||||||
"react-dom": "18.1.0",
|
"react-dom": "18.1.0",
|
||||||
"react-i18next": "^11.18.3",
|
"react-i18next": "^11.18.3",
|
||||||
"react-native": "0.70.5",
|
"react-native": "0.70.5",
|
||||||
|
"react-native-ionicons": "^4.6.5",
|
||||||
"react-native-paper": "^4.12.5",
|
"react-native-paper": "^4.12.5",
|
||||||
"react-native-reanimated": "~2.12.0",
|
"react-native-reanimated": "~2.12.0",
|
||||||
"react-native-safe-area-context": "4.4.1",
|
"react-native-safe-area-context": "4.4.1",
|
||||||
|
|||||||
181
front/views/SigninView.tsx
Normal file
181
front/views/SigninView.tsx
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useDispatch } from '../state/Store';
|
||||||
|
import { Translate, translate } from '../i18n/i18n';
|
||||||
|
import API, { APIError } from '../API';
|
||||||
|
import { setAccessToken } from '../state/UserSlice';
|
||||||
|
import { } from 'native-base';
|
||||||
|
import SigninForm from '../components/forms/signinform';
|
||||||
|
import TextButton from '../components/TextButton';
|
||||||
|
import { useNavigation } from '../Navigation';
|
||||||
|
import { string } from 'yup';
|
||||||
|
import { FormControl, Input, Stack, Center, Button, Text, Box, useToast } from 'native-base';
|
||||||
|
import { TouchableOpacity, Linking, View, StyleSheet } from 'react-native'
|
||||||
|
import TextFormField from '../components/UI/TextFormField';
|
||||||
|
import LinkBase from '../components/UI/LinkBase';
|
||||||
|
import SeparatorBase from '../components/UI/SeparatorBase';
|
||||||
|
import ButtonBase from '../components/UI/ButtonBase';
|
||||||
|
import { Image, Flex } from 'native-base';
|
||||||
|
import ImageBanner from '../assets/banner.jpg';
|
||||||
|
import TMPBase from '../components/UI/TMPBase';
|
||||||
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
import SettingBase from '../components/UI/SettingsBase';
|
||||||
|
|
||||||
|
const hanldeSignin = async (
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
apiSetter: (accessToken: string) => void
|
||||||
|
): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const apiAccess = await API.authenticate({ username, password });
|
||||||
|
apiSetter(apiAccess);
|
||||||
|
return translate('loggedIn');
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof APIError) return translate(error.userMessage);
|
||||||
|
if (error instanceof Error) return error.message;
|
||||||
|
return translate('unknownError');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SigninView = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const navigation = useNavigation();
|
||||||
|
const [formData, setFormData] = React.useState({
|
||||||
|
username: {
|
||||||
|
value: '',
|
||||||
|
error: null as string | null,
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
value: '',
|
||||||
|
error: null as string | null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const validationSchemas = {
|
||||||
|
username: string()
|
||||||
|
.min(3, translate('usernameTooShort'))
|
||||||
|
.max(20, translate('usernameTooLong'))
|
||||||
|
.required('Username is required'),
|
||||||
|
password: string()
|
||||||
|
.min(4, translate('passwordTooShort'))
|
||||||
|
.max(100, translate('passwordTooLong'))
|
||||||
|
.required('Password is required'),
|
||||||
|
};
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
|
||||||
|
const onSubmit= (username: string, password: string) => {
|
||||||
|
return hanldeSignin(username, password, (accessToken) =>
|
||||||
|
dispatch(setAccessToken(accessToken))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex direction='row' justifyContent="space-between" style={{ flex: 1, backgroundColor: '#101014'}}>
|
||||||
|
<Center style={{ flex: 1}}>
|
||||||
|
<Stack space={4} justifyContent="center" alignContent="center" alignItems="center" style={{ width: '100%', maxWidth: 420, padding: 16 }}>
|
||||||
|
<Text fontSize="4xl" textAlign="center">
|
||||||
|
Bienvenue !
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="xl" textAlign="center">
|
||||||
|
Continuez avec Google ou entrez vos coordonnées.
|
||||||
|
</Text>
|
||||||
|
<ButtonBase
|
||||||
|
type='outlined'
|
||||||
|
iconImage='https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/2008px-Google_%22G%22_Logo.svg.png'
|
||||||
|
title="Signin with google"
|
||||||
|
/>
|
||||||
|
<ButtonBase
|
||||||
|
type='menu'
|
||||||
|
icon='person'
|
||||||
|
title="Menu"
|
||||||
|
/>
|
||||||
|
<SettingBase icon='person' title='title' description='description'>coucou</SettingBase>
|
||||||
|
<SeparatorBase>or</SeparatorBase>
|
||||||
|
<TextFormField
|
||||||
|
error={formData.username.error}
|
||||||
|
icon='person'
|
||||||
|
placeholder="Username"
|
||||||
|
autoComplete="username"
|
||||||
|
value={formData.username.value}
|
||||||
|
onChangeText={(t) => {
|
||||||
|
let error: null | string = null;
|
||||||
|
validationSchemas.username
|
||||||
|
.validate(t)
|
||||||
|
.catch((e) => (error = e.message))
|
||||||
|
.finally(() => {
|
||||||
|
setFormData({ ...formData, username: { value: t, error } });
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
isRequired
|
||||||
|
/>
|
||||||
|
<TextFormField
|
||||||
|
isRequired
|
||||||
|
isSecret
|
||||||
|
error={formData.password.error}
|
||||||
|
icon='lock-closed'
|
||||||
|
placeholder="Password"
|
||||||
|
autoComplete="password"
|
||||||
|
value={formData.password.value}
|
||||||
|
onChangeText={(t) => {
|
||||||
|
let error: null | string = null;
|
||||||
|
validationSchemas.password
|
||||||
|
.validate(t)
|
||||||
|
.catch((e) => (error = e.message))
|
||||||
|
.finally(() => {
|
||||||
|
setFormData({ ...formData, password: { value: t, error } });
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<LinkBase onPress={() => console.log('Link clicked!')}>
|
||||||
|
{translate('forgottenPassword')}
|
||||||
|
</LinkBase>
|
||||||
|
|
||||||
|
<ButtonBase
|
||||||
|
type='outlined'
|
||||||
|
icon='alert'
|
||||||
|
title="Signin"
|
||||||
|
isDisabled={
|
||||||
|
formData.password.error !== null ||
|
||||||
|
formData.username.error !== null ||
|
||||||
|
formData.username.value === '' ||
|
||||||
|
formData.password.value === ''
|
||||||
|
}
|
||||||
|
onPress={async () => {
|
||||||
|
try {
|
||||||
|
const resp = await onSubmit(
|
||||||
|
formData.username.value,
|
||||||
|
formData.password.value
|
||||||
|
);
|
||||||
|
toast.show({ description: resp, colorScheme: 'secondary' });
|
||||||
|
} catch (e) {
|
||||||
|
toast.show({
|
||||||
|
description: e as string,
|
||||||
|
colorScheme: 'red',
|
||||||
|
avoidKeyboard: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text>Vous n'avez pas de compte ?</Text>
|
||||||
|
<LinkBase onPress={() => navigation.navigate('Signup', {})}>
|
||||||
|
Inscrivez-vous gratuitement
|
||||||
|
</LinkBase>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
|
<View style={{width: '50%', height: '100%', padding: 16}}>
|
||||||
|
<Image
|
||||||
|
source={ImageBanner}
|
||||||
|
alt="banner page"
|
||||||
|
style={{width: '100%', height: '100%', borderRadius: 8}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<LinearGradient
|
||||||
|
start={{x: 0, y: 0}}
|
||||||
|
end={{x: 1, y: 1}}
|
||||||
|
colors={['#101014', '#6075F9']}
|
||||||
|
style={{top: 0, bottom: 0, right: 0, left: 0, width: '100%', height: '100%', position: 'absolute', zIndex: -2}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SigninView;
|
||||||
214
front/views/SignupView.tsx
Normal file
214
front/views/SignupView.tsx
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useDispatch } from '../state/Store';
|
||||||
|
import { Translate, translate } from '../i18n/i18n';
|
||||||
|
import API, { APIError } from '../API';
|
||||||
|
import { setAccessToken } from '../state/UserSlice';
|
||||||
|
import { } from 'native-base';
|
||||||
|
import SigninForm from '../components/forms/signinform';
|
||||||
|
import TextButton from '../components/TextButton';
|
||||||
|
import { useNavigation } from '../Navigation';
|
||||||
|
import { string } from 'yup';
|
||||||
|
import { FormControl, Input, Stack, Center, Button, Text, Box, useToast } from 'native-base';
|
||||||
|
import { TouchableOpacity, Linking, View } from 'react-native'
|
||||||
|
import TextFormField from '../components/UI/TextFormField';
|
||||||
|
import LinkBase from '../components/UI/LinkBase';
|
||||||
|
import SeparatorBase from '../components/UI/SeparatorBase';
|
||||||
|
import ButtonBase from '../components/UI/ButtonBase';
|
||||||
|
import { Image, Flex } from 'native-base';
|
||||||
|
import ImageBanner from '../assets/banner.jpg';
|
||||||
|
|
||||||
|
const handleSignup = async (
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
email: string,
|
||||||
|
apiSetter: (accessToken: string) => void
|
||||||
|
): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const apiAccess = await API.createAccount({ username, password, email });
|
||||||
|
apiSetter(apiAccess);
|
||||||
|
return translate('loggedIn');
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof APIError) return translate(error.userMessage);
|
||||||
|
if (error instanceof Error) return error.message;
|
||||||
|
return translate('unknownError');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SigninView = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const navigation = useNavigation();
|
||||||
|
const [formData, setFormData] = React.useState({
|
||||||
|
username: {
|
||||||
|
value: '',
|
||||||
|
error: null as string | null,
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
value: '',
|
||||||
|
error: null as string | null,
|
||||||
|
},
|
||||||
|
repeatPassword: {
|
||||||
|
value: '',
|
||||||
|
error: null as string | null,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
value: '',
|
||||||
|
error: null as string | null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const validationSchemas = {
|
||||||
|
username: string()
|
||||||
|
.min(3, translate('usernameTooShort'))
|
||||||
|
.max(20, translate('usernameTooLong'))
|
||||||
|
.required('Username is required'),
|
||||||
|
email: string().email('Invalid email').required('Email is required'),
|
||||||
|
password: string()
|
||||||
|
.min(4, translate('passwordTooShort'))
|
||||||
|
.max(100, translate('passwordTooLong'))
|
||||||
|
// .matches(
|
||||||
|
// /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$-_%\^&\*])(?=.{8,})/,
|
||||||
|
// translate(
|
||||||
|
// "Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character"
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
.required('Password is required'),
|
||||||
|
};
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
|
||||||
|
const onSubmit= (username: string, email: string, password: string) => {
|
||||||
|
return handleSignup(username, password, email, (accessToken) =>
|
||||||
|
dispatch(setAccessToken(accessToken))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex direction='row' justifyContent="space-between" style={{ flex: 1, backgroundColor: '#101014'}}>
|
||||||
|
<Center style={{ flex: 1}}>
|
||||||
|
<Stack space={4} justifyContent="center" alignContent="center" alignItems="center" mx="4" style={{ width: '100%', maxWidth: 420, padding: 16 }}>
|
||||||
|
<Text fontSize="4xl" textAlign="center">Créer un compte</Text>
|
||||||
|
<Text fontSize="xl" textAlign="center">Apprendre le piano gratuitement et de manière ludique</Text>
|
||||||
|
<ButtonBase
|
||||||
|
isOutlined
|
||||||
|
iconImage='https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/2008px-Google_%22G%22_Logo.svg.png'
|
||||||
|
title="Signup with google"
|
||||||
|
/>
|
||||||
|
<SeparatorBase>or</SeparatorBase>
|
||||||
|
<TextFormField
|
||||||
|
error={formData.username.error}
|
||||||
|
icon='person'
|
||||||
|
placeholder="Username"
|
||||||
|
autoComplete="username"
|
||||||
|
value={formData.username.value}
|
||||||
|
onChangeText={(t) => {
|
||||||
|
let error: null | string = null;
|
||||||
|
validationSchemas.username
|
||||||
|
.validate(t)
|
||||||
|
.catch((e) => (error = e.message))
|
||||||
|
.finally(() => {
|
||||||
|
setFormData({ ...formData, username: { value: t, error } });
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
isRequired
|
||||||
|
/>
|
||||||
|
<TextFormField
|
||||||
|
error={formData.email.error}
|
||||||
|
icon='mail'
|
||||||
|
placeholder="Email"
|
||||||
|
autoComplete="email"
|
||||||
|
value={formData.email.value}
|
||||||
|
onChangeText={(t) => {
|
||||||
|
let error: null | string = null;
|
||||||
|
validationSchemas.email
|
||||||
|
.validate(t)
|
||||||
|
.catch((e) => (error = e.message))
|
||||||
|
.finally(() => {
|
||||||
|
setFormData({ ...formData, email: { value: t, error } });
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
isRequired
|
||||||
|
/>
|
||||||
|
<TextFormField
|
||||||
|
isRequired
|
||||||
|
isSecret
|
||||||
|
error={formData.password.error}
|
||||||
|
icon='lock-closed'
|
||||||
|
placeholder="Password"
|
||||||
|
autoComplete="password-new"
|
||||||
|
value={formData.password.value}
|
||||||
|
onChangeText={(t) => {
|
||||||
|
let error: null | string = null;
|
||||||
|
validationSchemas.password
|
||||||
|
.validate(t)
|
||||||
|
.catch((e) => (error = e.message))
|
||||||
|
.finally(() => {
|
||||||
|
setFormData({ ...formData, password: { value: t, error } });
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextFormField
|
||||||
|
isRequired
|
||||||
|
isSecret
|
||||||
|
error={formData.repeatPassword.error}
|
||||||
|
icon='lock-closed'
|
||||||
|
placeholder="Repeat password"
|
||||||
|
autoComplete="password-new"
|
||||||
|
value={formData.repeatPassword.value}
|
||||||
|
onChangeText={(t) => {
|
||||||
|
let error: null | string = null;
|
||||||
|
validationSchemas.password
|
||||||
|
.validate(t)
|
||||||
|
.catch((e) => (error = e.message))
|
||||||
|
.finally(() => {
|
||||||
|
if (!error && t !== formData.password.value) {
|
||||||
|
error = translate('passwordsDontMatch');
|
||||||
|
}
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
repeatPassword: { value: t, error },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ButtonBase
|
||||||
|
title="Signup"
|
||||||
|
isDisabled={
|
||||||
|
formData.password.error !== null ||
|
||||||
|
formData.username.error !== null ||
|
||||||
|
formData.repeatPassword.error !== null ||
|
||||||
|
formData.email.error !== null ||
|
||||||
|
formData.username.value === '' ||
|
||||||
|
formData.password.value === '' ||
|
||||||
|
formData.repeatPassword.value === '' ||
|
||||||
|
formData.repeatPassword.value === ''
|
||||||
|
}
|
||||||
|
onPress={async () => {
|
||||||
|
try {
|
||||||
|
const resp = await onSubmit(
|
||||||
|
formData.username.value,
|
||||||
|
formData.password.value,
|
||||||
|
formData.email.value
|
||||||
|
);
|
||||||
|
toast.show({ description: resp });
|
||||||
|
} catch (e) {
|
||||||
|
toast.show({ description: e as string });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text>Vous avez déjà un compte ?</Text>
|
||||||
|
<LinkBase onPress={() => navigation.navigate('Login', {})}>
|
||||||
|
S'identifier
|
||||||
|
</LinkBase>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
|
<View style={{width: '50%', height: '100%', padding: 16}}>
|
||||||
|
<Image
|
||||||
|
source={ImageBanner}
|
||||||
|
alt="banner page"
|
||||||
|
style={{width: '100%', height: '100%', borderRadius: 8}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SigninView;
|
||||||
@@ -2884,6 +2884,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
merge-options "^3.0.4"
|
merge-options "^3.0.4"
|
||||||
|
|
||||||
|
"@react-native-community/blur@^4.3.2":
|
||||||
|
version "4.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-native-community/blur/-/blur-4.3.2.tgz#185a2c7dd03ba168cc95069bc4742e9505fd6c6c"
|
||||||
|
integrity sha512-0ID+pyZKdC4RdgC7HePxUQ6JmsbNrgz03u+6SgqYpmBoK/rE+7JffqIw7IEsfoKitLEcRNLGekIBsfwCqiEkew==
|
||||||
|
|
||||||
"@react-native-community/cli-clean@^9.2.1":
|
"@react-native-community/cli-clean@^9.2.1":
|
||||||
version "9.2.1"
|
version "9.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-9.2.1.tgz#198c5dd39c432efb5374582073065ff75d67d018"
|
resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-9.2.1.tgz#198c5dd39c432efb5374582073065ff75d67d018"
|
||||||
@@ -9126,6 +9131,11 @@ expo-keep-awake@~11.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-11.0.1.tgz#ee354465892a94040ffe09901b85b469e7d54fb3"
|
resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-11.0.1.tgz#ee354465892a94040ffe09901b85b469e7d54fb3"
|
||||||
integrity sha512-44ZjgLE4lnce2d40Pv8xsjMVc6R5GvgHOwZfkLYtGmgYG9TYrEJeEj5UfSeweXPL3pBFhXKfFU8xpGYMaHdP0A==
|
integrity sha512-44ZjgLE4lnce2d40Pv8xsjMVc6R5GvgHOwZfkLYtGmgYG9TYrEJeEj5UfSeweXPL3pBFhXKfFU8xpGYMaHdP0A==
|
||||||
|
|
||||||
|
expo-linear-gradient@~12.0.1:
|
||||||
|
version "12.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/expo-linear-gradient/-/expo-linear-gradient-12.0.1.tgz#452f793b0463ddf313aad431552f23acc85f5d64"
|
||||||
|
integrity sha512-TMl/wBTVQOliL4S3DS5Aa3UFfVySr0mdJEHLG6kfBdMCLkr+tfLI2rGyJ+scS7xgMsvhTIaurhf1+Z0sL3aLCg==
|
||||||
|
|
||||||
expo-linking@~3.3.1:
|
expo-linking@~3.3.1:
|
||||||
version "3.3.1"
|
version "3.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/expo-linking/-/expo-linking-3.3.1.tgz#253b183321e54cb6fa1a667a53d4594aa88a3357"
|
resolved "https://registry.yarnpkg.com/expo-linking/-/expo-linking-3.3.1.tgz#253b183321e54cb6fa1a667a53d4594aa88a3357"
|
||||||
@@ -15762,6 +15772,11 @@ react-native-gradle-plugin@^0.70.3:
|
|||||||
resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.70.3.tgz#cbcf0619cbfbddaa9128701aa2d7b4145f9c4fc8"
|
resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.70.3.tgz#cbcf0619cbfbddaa9128701aa2d7b4145f9c4fc8"
|
||||||
integrity sha512-oOanj84fJEXUg9FoEAQomA8ISG+DVIrTZ3qF7m69VQUJyOGYyDZmPqKcjvRku4KXlEH6hWO9i4ACLzNBh8gC0A==
|
integrity sha512-oOanj84fJEXUg9FoEAQomA8ISG+DVIrTZ3qF7m69VQUJyOGYyDZmPqKcjvRku4KXlEH6hWO9i4ACLzNBh8gC0A==
|
||||||
|
|
||||||
|
react-native-ionicons@^4.6.5:
|
||||||
|
version "4.6.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-ionicons/-/react-native-ionicons-4.6.5.tgz#b792ec8896381e67ff237eba955e38afe7ceb7a4"
|
||||||
|
integrity sha512-s2Ia7M5t609LE9LWygMj3ALVPUlKhK7R9XcMb67fP4EYJv0oLcwg5pc+8ftv9XXaUuTW/WgL3zJlBYxAvtvMJg==
|
||||||
|
|
||||||
react-native-iphone-x-helper@^1.3.1:
|
react-native-iphone-x-helper@^1.3.1:
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010"
|
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010"
|
||||||
|
|||||||
Reference in New Issue
Block a user