diff --git a/src/assets/apple-logo.png b/src/assets/apple-logo.png new file mode 100644 index 0000000..9577267 Binary files /dev/null and b/src/assets/apple-logo.png differ diff --git a/src/assets/google-logo.png b/src/assets/google-logo.png new file mode 100644 index 0000000..6c94ae3 Binary files /dev/null and b/src/assets/google-logo.png differ diff --git a/src/assets/logo.png b/src/assets/logo.png index 4b9e1df..e2667b5 100644 Binary files a/src/assets/logo.png and b/src/assets/logo.png differ diff --git a/src/components/MyButton.tsx b/src/components/MyButton.tsx new file mode 100644 index 0000000..0b833d4 --- /dev/null +++ b/src/components/MyButton.tsx @@ -0,0 +1,92 @@ +import {View} from 'react-native'; +import { + Image, + ImageSourcePropType, + ImageStyle, + StyleProp, + Text, + TouchableOpacity, + ViewStyle, +} from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; + +interface MyIconButtonProps { + image: ImageSourcePropType; + imageStyle?: StyleProp; + text: string; +} + +export function MyIconButton({image, imageStyle, text}: MyIconButtonProps) { + return ( + + + + {text} + + + ); +} + +interface MyButtonProps { + style?: StyleProp; + type: 'primary' | 'secondary'; + text: string; + onPress?: () => void; +} + +export function MyButton({style, type, text, onPress}: MyButtonProps) { + const ButtonText = () => ( + + {text} + + ); + + return ( + + {type === 'primary' ? ( + + + + ) : ( + + + + )} + + ); +} diff --git a/src/components/MyClickableText.tsx b/src/components/MyClickableText.tsx new file mode 100644 index 0000000..40038cd --- /dev/null +++ b/src/components/MyClickableText.tsx @@ -0,0 +1,16 @@ +import {StyleProp, Text, TextStyle} from 'react-native'; +import {TouchableOpacity} from 'react-native'; + +interface MyClickableTextProps { + textStyle?: StyleProp; + text: string; + onPress?: () => void; +} + +export function MyClickableText({textStyle, text, onPress}: MyClickableTextProps) { + return ( + + {text} + + ); +} diff --git a/src/components/MyDivider.tsx b/src/components/MyDivider.tsx new file mode 100644 index 0000000..c4017da --- /dev/null +++ b/src/components/MyDivider.tsx @@ -0,0 +1,19 @@ +import {Text, View} from 'react-native'; + +export function MyDivider() { + return ; +} + +interface MyDividerWithTextProps { + text?: string; +} + +export function MyDividerWithText({text}: MyDividerWithTextProps) { + return ( + + + {text} + + + ); +} diff --git a/src/components/MyIcon.tsx b/src/components/MyIcon.tsx new file mode 100644 index 0000000..5ecb418 --- /dev/null +++ b/src/components/MyIcon.tsx @@ -0,0 +1,12 @@ +import {ColorValue} from 'react-native'; +import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; + +interface MyIconProps { + name: string; + size?: number | undefined; + color?: number | ColorValue | undefined +} + +export function MyIcon({name, size, color}: MyIconProps) { + return ; +} diff --git a/src/components/MyInput.tsx b/src/components/MyInput.tsx new file mode 100644 index 0000000..4661bc4 --- /dev/null +++ b/src/components/MyInput.tsx @@ -0,0 +1,39 @@ +import {KeyboardTypeOptions, Text, TextInput, View} from 'react-native'; +import {MyIcon} from './MyIcon'; + +interface MyIconInputProps { + text: string; + iconName: string; + keyboardType?: KeyboardTypeOptions; + secureTextEntry?: boolean | undefined; +} + +export function MyIconInput({text, iconName, keyboardType, secureTextEntry}: MyIconInputProps) { + return ( + + + + + + + {text} + + + + ); +} diff --git a/src/components/MyScreenContainer.tsx b/src/components/MyScreenContainer.tsx new file mode 100644 index 0000000..226f323 --- /dev/null +++ b/src/components/MyScreenContainer.tsx @@ -0,0 +1,13 @@ +import {ReactNode} from 'react'; +import {StyleProp, View, ViewStyle} from 'react-native'; + +interface MyScreenContainerProps { + children: ReactNode; + style?: StyleProp; +} + +export function MyScreenContainer({children, style}: MyScreenContainerProps) { + return ( + {children} + ); +} diff --git a/src/helper/linking.ts b/src/helper/linking.ts new file mode 100644 index 0000000..00a3256 --- /dev/null +++ b/src/helper/linking.ts @@ -0,0 +1,11 @@ +import {Linking} from 'react-native'; + +export function OpenURL(url: string) { + const supported = Linking.canOpenURL(url); + + if (!supported) { + console.log("Can't handle url: " + url); + } else { + return Linking.openURL(url); + } +} diff --git a/src/navigation/navigation.tsx b/src/navigation/navigation.tsx index 05dc47e..4466d7d 100644 --- a/src/navigation/navigation.tsx +++ b/src/navigation/navigation.tsx @@ -4,8 +4,6 @@ import { BottomTabBarProps, createBottomTabNavigator, } from '@react-navigation/bottom-tabs'; - -import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import CalendarTab from './tabs/main/CalendarTab'; import ChatsTab from './tabs/main/ChatsTab'; import MapTab from './tabs/main/MapTab'; @@ -15,20 +13,30 @@ import ProfileTab, { import {FadeInView, SlideFromLeftView} from '@helper/animations'; import {Text, TouchableOpacity, View} from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; +import { + RegistrationScreenAnim, + RegistrationStackNavigatorParamList, +} from './registration/registration'; +import {NativeStackNavigationProp} from '@react-navigation/native-stack'; +import { MyIcon } from '@components/MyIcon'; export type RootStackNavigatorParamList = { Home: NavigatorScreenParams; - //Register: NavigatorScreenParams; + Registration: NavigatorScreenParams; }; +export type RootScreenNavigationProp = + NativeStackNavigationProp; + export default function Navigation() { return ( + }} + initialRouteName="Registration"> - + ); } @@ -143,11 +151,9 @@ function CustomTabBar(props: BottomTabBarProps) { width: '100%', alignItems: 'center', }}> - + size={20} /> {isFocused && ( {label.toString()} )} @@ -159,6 +165,8 @@ function CustomTabBar(props: BottomTabBarProps) { ); } +const Tab = createBottomTabNavigator(); + function HomeStack() { return ( ); } - -function RegisterStack() { - return <>; -} - -const Tab = createBottomTabNavigator(); diff --git a/src/navigation/registration/registration.tsx b/src/navigation/registration/registration.tsx new file mode 100644 index 0000000..4bc550c --- /dev/null +++ b/src/navigation/registration/registration.tsx @@ -0,0 +1,518 @@ +import {MyScreenContainer} from '@components/MyScreenContainer'; +import {SlideFromLeftView} from '@helper/animations'; +import {RootScreenNavigationProp} from '@navigation/navigation'; +import {useNavigation} from '@react-navigation/native'; +import { + createNativeStackNavigator, + NativeStackNavigationOptions, +} from '@react-navigation/native-stack'; +import {Image, StyleSheet, Text, TextInput, View} from 'react-native'; + +import GoogleLogo from '@assets/google-logo.png'; +import AppleLogo from '@assets/apple-logo.png'; +import Logo from '@assets/logo.png'; + +import {MyDividerWithText} from '@components/MyDivider'; +import {MyButton, MyIconButton} from '@components/MyButton'; +import {MyClickableText} from '@components/MyClickableText'; +import {OpenURL} from '@helper/linking'; +import {Constants} from '@utils/utils'; +import {MyIconInput} from '@components/MyInput'; +import {useRef, useState} from 'react'; +import {KeyboardAvoidingView} from 'react-native'; + +export type RegistrationStackNavigatorParamList = { + SignUpPreview: undefined; + SignUpStepUsername: undefined; + SignUpStepPhoneNumber: undefined; + SignUpStepVerifyPhoneNumber: undefined; + SignUpStepPassword: undefined; + SignUpStepAccountName: undefined; + LoginPreview: undefined; + Login: undefined; +}; + +const RegistrationStack = + createNativeStackNavigator(); + +export function RegistrationScreenAnim(props: any) { + return ( + + + + ); +} + +const headerStyle: NativeStackNavigationOptions = { + headerShown: true, + headerStyle: {backgroundColor: '#212137'}, + headerTitleAlign: 'center', + headerTitle: '', +}; + +export function RegistrationScreen() { + return ( + + + + + + + + + + + ); +} + +function SignUpPreview() { + return ; +} + +function LoginPreview() { + return ; +} + +function ThirdAuthButtons() { + return ( + <> + + + + + ); +} + +function RegistrationPreview({type}: {type: 'login' | 'signup'}) { + const navigation = useNavigation(); + + return ( + + + + + + + + {type === 'login' + ? 'Time to get to know the next party' + : 'Find the next party near you'} + + + + + + + + + { + navigation.navigate( + 'Registration', + type === 'login' + ? {screen: 'Login'} + : {screen: 'SignUpStepUsername'}, + ); + }} + /> + + {type === 'login' && ( + + By signing up, you agree to our + OpenURL(Constants.REGISTRATION.TERMS_URL)} + /> + , + OpenURL(Constants.REGISTRATION.PRIVACY_POLICY_URL)} + /> + and + OpenURL(Constants.REGISTRATION.COOKIE_USE_URL)} + /> + . + + )} + + + {type === 'login' ? ( + Don't have an account? + ) : ( + Have already an account? + )} + + { + navigation.navigate( + 'Registration', + type === 'login' + ? {screen: 'SignUpPreview'} + : {screen: 'LoginPreview'}, + ); + }} + /> + + + + ); +} + +function ContentContainer({children}: {children: React.ReactNode}) { + return ( + <> + + {children} + + ); +} + +function Title({text}: {text: string}) { + return ( + + {text} + + ); +} + +function Login() { + const navigation = useNavigation(); + + return ( + + + + + <View style={{gap: 12}}> + <MyIconInput text="PHONE NUMBER OR ACCOUNT NAME" iconName="person" /> + <MyIconInput text="PASSWORD" iconName="lock" secureTextEntry /> + + <MyButton + type="secondary" + text={'Login'} + style={{marginBottom: 20}} + onPress={() => { + navigation.navigate('Home'); + }} + /> + </View> + </ContentContainer> + </MyScreenContainer> + ); +} + +function SignUpStepUsername() { + const navigation = useNavigation<RootScreenNavigationProp>(); + + return ( + <MyScreenContainer + style={{ + flex: 1, + flexDirection: 'column', + backgroundColor: '#212137', + }}> + <ContentContainer> + <Title text="Let's get started, what's your name?" /> + <Text>The name will be displayed on your profil overview</Text> + + <View style={{gap: 12, marginTop: 20}}> + <MyIconInput text="Username" iconName="person" /> + + <MyButton + type="secondary" + text={'Next'} + style={{marginBottom: 20}} + onPress={() => { + navigation.navigate('Registration', { + screen: 'SignUpStepPhoneNumber', + }); + }} + /> + </View> + </ContentContainer> + </MyScreenContainer> + ); +} + +function SignUpStepPhoneNumber() { + const navigation = useNavigation<RootScreenNavigationProp>(); + + return ( + <MyScreenContainer + style={{ + flex: 1, + flexDirection: 'column', + backgroundColor: '#212137', + }}> + <ContentContainer> + <Title text="Create your account using your phone number" /> + + <View style={{gap: 12, marginTop: 20}}> + <MyIconInput + text="Phone" + iconName="phone-iphone" + keyboardType="phone-pad" + /> + + <MyButton + type="secondary" + text={'Next'} + style={{marginBottom: 20}} + onPress={() => { + navigation.navigate('Registration', { + screen: 'SignUpStepVerifyPhoneNumber', + }); + }} + /> + </View> + </ContentContainer> + </MyScreenContainer> + ); +} + +interface ConfirmationCodeInputProps { + // Zusätzliche Props für das ConfirmationCodeInput-Komponente hier hinzufügen +} + +const ConfirmationCodeInput: React.FC<ConfirmationCodeInputProps> = () => { + const [confirmationCode, setConfirmationCode] = useState([ + '', + '', + '', + '', + '', + '', + ]); + const inputRefs = useRef<TextInput[]>([]); + + const focusInput = (index: number) => { + if (inputRefs.current[index]) { + inputRefs.current[index].focus(); + } + }; + + const handleTextChange = (text: string, index: number) => { + const newConfirmationCode = [...confirmationCode]; + newConfirmationCode[index] = text; + + // Fokus zum nächsten Input verschieben, wenn der aktuelle Input nicht leer ist + if (text !== '' && index < confirmationCode.length - 1) { + focusInput(index + 1); + } + + setConfirmationCode(newConfirmationCode); + }; + + return ( + <View style={styles.container}> + {confirmationCode.map((digit, index) => ( + <TextInput + key={index} + ref={ref => (inputRefs.current[index] = ref!)} + style={styles.input} + keyboardType="number-pad" + maxLength={1} + onChangeText={text => handleTextChange(text, index)} + value={digit} + /> + ))} + </View> + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + justifyContent: 'center', + }, + input: { + height: 40, + width: 40, + borderColor: 'gray', + borderWidth: 1, + textAlign: 'center', + margin: 5, + borderRadius: 5, + }, +}); + +function SignUpStepVerifyPhoneNumber() { + const navigation = useNavigation<RootScreenNavigationProp>(); + + return ( + <MyScreenContainer + style={{ + flex: 1, + flexDirection: 'column', + backgroundColor: '#212137', + }}> + <ContentContainer> + <Title text="We sent you a code" /> + <Text>Enter it below to verify +49 15** ******43 </Text> + + <KeyboardAvoidingView style={{gap: 12, marginTop: 20}}> + <ConfirmationCodeInput /> + + <MyButton + type="secondary" + text={'Next'} + style={{marginBottom: 2}} + onPress={() => { + navigation.navigate('Registration', { + screen: 'SignUpStepPassword', + }); + }} + /> + <View style={{alignItems: 'center'}}> + <MyClickableText text="Resend code" /> + </View> + </KeyboardAvoidingView> + </ContentContainer> + </MyScreenContainer> + ); +} + +function SignUpStepPassword() { + const navigation = useNavigation<RootScreenNavigationProp>(); + + return ( + <MyScreenContainer + style={{ + flex: 1, + flexDirection: 'column', + backgroundColor: '#212137', + }}> + <ContentContainer> + <Title text="You'll need a password" /> + <Text>Make sure it's 8 characters or more</Text> + + <View style={{gap: 12, marginTop: 20}}> + <MyIconInput text="PASSWORD" iconName="lock" secureTextEntry /> + + <MyButton + type="secondary" + text={'Next'} + style={{marginBottom: 2}} + onPress={() => { + navigation.navigate('Registration', { + screen: 'SignUpStepAccountName', + }); + }} + /> + </View> + </ContentContainer> + </MyScreenContainer> + ); +} + +function SignUpStepAccountName() { + const navigation = useNavigation<RootScreenNavigationProp>(); + + return ( + <MyScreenContainer + style={{ + flex: 1, + flexDirection: 'column', + backgroundColor: '#212137', + }}> + <ContentContainer> + <Title text="Next, create your account name" /> + <Text> + Your account name is unique and is used for friends to find you. + </Text> + + <View style={{gap: 12, marginTop: 20}}> + <MyIconInput text="ACCOUNT NAME" iconName="person" /> + + <MyButton + type="primary" + text={'Get Started'} + style={{marginBottom: 2}} + onPress={() => { + navigation.navigate('Home'); + }} + /> + </View> + </ContentContainer> + </MyScreenContainer> + ); +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 0000000..d7edef7 --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,7 @@ +export const Constants = { + REGISTRATION: { + TERMS_URL: 'https://www.google.com', + PRIVACY_POLICY_URL: 'https://www.google.com', + COOKIE_USE_URL: 'https://www.google.com', + }, +}; diff --git a/tsconfig.json b/tsconfig.json index c4ef48a..d687fbd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,45 +6,19 @@ "baseUrl": ".", "target": "ESNext", "paths": { - "@redux/*": [ - "src/redux/*" - ], - "@lang/*": [ - "src/lang/*" - ], - "@pages/*": [ - "src/pages/*" - ], - "@api/*": [ - "src/api/*" - ], - "@assets/*": [ - "src/assets/*" - ], - "@components/*": [ - "src/components/*" - ], - "@scenes/*": [ - "src/scenes/*" - ], - "@theme/*": [ - "src/theme/*" - ], - "@utils/*": [ - "src/utils/*" - ], - "@navigation/*": [ - "src/navigation/*" - ], - "@configs/*": [ - "src/configs/*" - ], - "@helper/*": [ - "src/helper/*" - ], - "@user/*": [ - "src/user/*" - ], - }, + "@redux/*": ["src/redux/*"], + "@lang/*": ["src/lang/*"], + "@pages/*": ["src/pages/*"], + "@api/*": ["src/api/*"], + "@assets/*": ["src/assets/*"], + "@components/*": ["src/components/*"], + "@scenes/*": ["src/scenes/*"], + "@theme/*": ["src/theme/*"], + "@utils/*": ["src/utils/*"], + "@navigation/*": ["src/navigation/*"], + "@configs/*": ["src/configs/*"], + "@helper/*": ["src/helper/*"], + "@user/*": ["src/user/*"] + } } -} \ No newline at end of file +}