This commit is contained in:
StarAppeal
2024-12-13 00:02:28 +01:00
parent 67fcda6bb6
commit 803143e764
8 changed files with 182 additions and 47 deletions
+7 -1
View File
@@ -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
View File
@@ -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
+24
View File
@@ -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",
+1
View File
@@ -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",
+8 -13
View File
@@ -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>
</>
);
}
+58
View File
@@ -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
}}
/>
}
/>
);
}
+49
View File
@@ -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,
},
});
+33 -30
View File
@@ -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: {