refactor
This commit is contained in:
@@ -33,7 +33,13 @@
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
],
|
||||
"expo-secure-store"
|
||||
"expo-secure-store",
|
||||
[
|
||||
"expo-image-picker",
|
||||
{
|
||||
"photosPermission": "Allow $(PRODUCT_NAME) to access your photos"
|
||||
}
|
||||
]
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true
|
||||
|
||||
+2
-3
@@ -11,6 +11,7 @@ import BackButton from "../src/components/BackButton";
|
||||
import {useAuth} from "@/src/context/AuthProvider";
|
||||
import {useRouter} from "expo-router";
|
||||
import ThemeToggleButton from "@/src/components/ThemeToggleButton";
|
||||
import PasswordInput from "@/src/components/PasswordInput";
|
||||
|
||||
|
||||
export default function LoginScreen() {
|
||||
@@ -62,14 +63,12 @@ export default function LoginScreen() {
|
||||
errorText={error?.message}
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
<ThemedTextInput
|
||||
label="Password"
|
||||
<PasswordInput
|
||||
returnKeyType="done"
|
||||
value={password.value}
|
||||
onChangeText={(text: string) => setPassword({value: text})}
|
||||
error={!!error && error?.id === "password"}
|
||||
errorText={error?.message}
|
||||
secureTextEntry
|
||||
/>
|
||||
<ThemedButton mode="outlined" onPress={onLoginPressed}>
|
||||
Log in
|
||||
|
||||
Generated
+24
@@ -31,6 +31,7 @@
|
||||
"react-dom": "18.3.1",
|
||||
"react-native": "0.76.5",
|
||||
"react-native-gesture-handler": "^2.21.2",
|
||||
"react-native-modal": "^13.0.1",
|
||||
"react-native-paper": "^5.12.5",
|
||||
"react-native-reanimated": "~3.16.3",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
@@ -15683,6 +15684,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-animatable": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-animatable/-/react-native-animatable-1.3.3.tgz",
|
||||
"integrity": "sha512-2ckIxZQAsvWn25Ho+DK3d1mXIgj7tITkrS4pYDvx96WyOttSvzzFeQnM2od0+FUMzILbdHDsDEqZvnz1DYNQ1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-gesture-handler": {
|
||||
"version": "2.21.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.21.2.tgz",
|
||||
@@ -15723,6 +15733,20 @@
|
||||
"react-native": ">=0.73.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-modal": {
|
||||
"version": "13.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-13.0.1.tgz",
|
||||
"integrity": "sha512-UB+mjmUtf+miaG/sDhOikRfBOv0gJdBU2ZE1HtFWp6UixW9jCk/bhGdHUgmZljbPpp0RaO/6YiMmQSSK3kkMaw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.6.2",
|
||||
"react-native-animatable": "1.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": ">=0.65.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-paper": {
|
||||
"version": "5.12.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.12.5.tgz",
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"react-dom": "18.3.1",
|
||||
"react-native": "0.76.5",
|
||||
"react-native-gesture-handler": "^2.21.2",
|
||||
"react-native-modal": "^13.0.1",
|
||||
"react-native-paper": "^5.12.5",
|
||||
"react-native-reanimated": "~3.16.3",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import React, {useState} from "react";
|
||||
import {Button, Paragraph} from "react-native-paper";
|
||||
import ThemedTextInput from "@/src/components/themed/ThemedTextInput";
|
||||
import {useTheme} from "@/src/context/ThemeProvider";
|
||||
import {StyleSheet, View, Modal} from "react-native";
|
||||
import {StyleSheet, View} from "react-native";
|
||||
import {RestService} from "@/src/services/RestService";
|
||||
import {useAuth} from "@/src/context/AuthProvider";
|
||||
import CustomModal from "@/src/components/themed/CustomModal";
|
||||
import PasswordInput from "@/src/components/PasswordInput";
|
||||
|
||||
|
||||
export default function ChangePasswordModal() {
|
||||
const {theme} = useTheme();
|
||||
const {token: jwtToken} = useAuth();
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [apiResponse, setApiResponse] = useState<{ success: boolean, message: string } | null>(null);
|
||||
@@ -38,7 +39,6 @@ export default function ChangePasswordModal() {
|
||||
|
||||
};
|
||||
const resetModal = () => {
|
||||
setIsVisible(false);
|
||||
setApiResponse(null);
|
||||
setPassword("");
|
||||
setConfirmPassword("");
|
||||
@@ -46,10 +46,7 @@ export default function ChangePasswordModal() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button mode="outlined" onPress={() => setIsVisible(true)}>
|
||||
Passwort ändern
|
||||
</Button>
|
||||
<Modal visible={isVisible} onDismiss={resetModal} animationType={"slide"} transparent={true}>
|
||||
<CustomModal resetCallback={resetModal} buttonTitle="Passwort ändern">
|
||||
<View style={styles.modalContent}>
|
||||
<Paragraph>Passwort ändern</Paragraph>
|
||||
{apiResponse && apiResponse.message && (
|
||||
@@ -60,19 +57,17 @@ export default function ChangePasswordModal() {
|
||||
</View>
|
||||
)}
|
||||
|
||||
<ThemedTextInput
|
||||
<PasswordInput
|
||||
label="Passwort"
|
||||
returnKeyType="next"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
secureTextEntry
|
||||
/>
|
||||
<ThemedTextInput
|
||||
<PasswordInput
|
||||
label="Passwort bestätigen"
|
||||
returnKeyType="done"
|
||||
value={confirmPassword}
|
||||
onChangeText={setConfirmPassword}
|
||||
secureTextEntry
|
||||
/>
|
||||
|
||||
<View style={styles.buttonGroup}>
|
||||
@@ -84,7 +79,7 @@ export default function ChangePasswordModal() {
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</CustomModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { TextInput, Platform } from "react-native";
|
||||
import ThemedTextInput, { ThemedTextInputProps } from "@/src/components/themed/ThemedTextInput";
|
||||
import { TextInput as PaperTextInput } from "react-native-paper";
|
||||
|
||||
export default function PasswordInput(props: ThemedTextInputProps) {
|
||||
const [hidePass, setHidePass] = useState(true);
|
||||
const [text, setText] = useState(""); // Textinhalt verwalten
|
||||
const [cursorPosition, setCursorPosition] = useState<number | null>(null);
|
||||
const inputRef = useRef<TextInput>(null);
|
||||
|
||||
const handleCursorReset = () => {
|
||||
if (cursorPosition === null) return;
|
||||
|
||||
if (Platform.OS === "web") {
|
||||
// Für Web: `setSelectionRange` verwenden
|
||||
setTimeout(() => {
|
||||
const inputElement = document.activeElement as HTMLInputElement;
|
||||
if (inputElement) {
|
||||
inputElement.setSelectionRange(cursorPosition, cursorPosition);
|
||||
}
|
||||
}, 0);
|
||||
} else {
|
||||
// Für mobile Plattformen: `setNativeProps` verwenden
|
||||
setTimeout(() => {
|
||||
inputRef.current?.setNativeProps({
|
||||
selection: { start: cursorPosition, end: cursorPosition },
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemedTextInput
|
||||
{...props}
|
||||
label="Password"
|
||||
value={text} // Textwert
|
||||
secureTextEntry={hidePass}
|
||||
ref={inputRef} // Referenz hinzufügen
|
||||
onChangeText={(input) => {
|
||||
setText(input);
|
||||
}}
|
||||
onSelectionChange={(e) => {
|
||||
// Cursor-Position speichern
|
||||
setCursorPosition(e.nativeEvent.selection.start);
|
||||
}}
|
||||
right={
|
||||
<PaperTextInput.Icon
|
||||
icon={hidePass ? "eye" : "eye-off"}
|
||||
onPress={() => {
|
||||
setHidePass(!hidePass);
|
||||
handleCursorReset(); // Cursor-Position zurücksetzen
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import React, {useState} from "react";
|
||||
import {Button} from "react-native-paper";
|
||||
import {StyleSheet, View} from "react-native";
|
||||
import Modal from "react-native-modal";
|
||||
|
||||
export interface Props {
|
||||
resetCallback: () => void;
|
||||
children: React.ReactNode;
|
||||
buttonTitle: string;
|
||||
}
|
||||
|
||||
export default function CustomModal({resetCallback, children, buttonTitle}: Props) {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
const resetModal = () => {
|
||||
setIsVisible(false);
|
||||
resetCallback();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button mode="outlined" onPress={() => setIsVisible(true)}>
|
||||
{buttonTitle}
|
||||
</Button>
|
||||
<Modal isVisible={isVisible} onModalHide={resetModal} onBackdropPress={resetModal} onBackButtonPress={resetModal}>
|
||||
<View style={styles.modalContent}>
|
||||
{children}
|
||||
</View>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modalContent: {
|
||||
padding: 16,
|
||||
borderRadius: 8,
|
||||
},
|
||||
apiResponseBox: {
|
||||
marginBottom: 8,
|
||||
marginTop: 8,
|
||||
padding: 8,
|
||||
},
|
||||
buttonGroup: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
marginTop: 16,
|
||||
},
|
||||
});
|
||||
@@ -1,43 +1,46 @@
|
||||
import React from "react";
|
||||
import {StyleSheet, Text, View} from "react-native";
|
||||
import {TextInput as Input} from "react-native-paper";
|
||||
import {useTheme} from "@/src/context/ThemeProvider";
|
||||
import React, { forwardRef } from "react";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
import { TextInput as Input, TextInputProps } from "react-native-paper"; // Importiere den korrekten Typ
|
||||
import { useTheme } from "@/src/context/ThemeProvider";
|
||||
|
||||
type Props = {
|
||||
export type ThemedTextInputProps = TextInputProps & { // Erweitere die Typen um die Eigenschaften von TextInput
|
||||
errorText?: string;
|
||||
description?: string;
|
||||
error?: boolean;
|
||||
[x: string]: any;
|
||||
};
|
||||
|
||||
export default function ThemedTextInput({errorText, description, error, ...props}: Props) {
|
||||
const {theme} = useTheme();
|
||||
const ThemedTextInput = forwardRef<any, ThemedTextInputProps>(
|
||||
({ errorText, description, error, ...props }, ref) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
if (error && !errorText) {
|
||||
console.log("ErrorText is missing! Please provide an errorText prop!");
|
||||
}
|
||||
|
||||
if (error && !errorText) {
|
||||
console.log("ErrorText is missing! Please provide an errorText prop!");
|
||||
const errorStyle = {
|
||||
fontSize: 13,
|
||||
color: theme.colors.error,
|
||||
paddingTop: 8,
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Input
|
||||
underlineColor="transparent"
|
||||
mode="outlined"
|
||||
{...props} // Übergib alle Props an Input
|
||||
ref={ref} // Übergibt die ref an die Input-Komponente
|
||||
/>
|
||||
{description && !error ? (
|
||||
<Text style={styles.description}>{description}</Text>
|
||||
) : null}
|
||||
{error && <Text style={errorStyle}>{errorText}</Text>}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const errorStyle = {
|
||||
fontSize: 13,
|
||||
color: theme.colors.error,
|
||||
paddingTop: 8,
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Input
|
||||
underlineColor="transparent"
|
||||
mode="outlined"
|
||||
{...props}
|
||||
/>
|
||||
{description && !error ? (
|
||||
<Text style={styles.description}>{description}</Text>
|
||||
) : null}
|
||||
{error && <Text style={errorStyle}>{errorText}</Text>}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
export default ThemedTextInput;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
||||
Reference in New Issue
Block a user