From 803143e7645a69ea48eb3ed60b719e8e24fea5bb Mon Sep 17 00:00:00 2001 From: StarAppeal Date: Fri, 13 Dec 2024 00:02:28 +0100 Subject: [PATCH] refactor --- app.json | 8 ++- app/login.tsx | 5 +- package-lock.json | 24 +++++++++ package.json | 1 + src/components/ChangePasswordModal.tsx | 21 +++----- src/components/PasswordInput.tsx | 58 +++++++++++++++++++++ src/components/themed/CustomModal.tsx | 49 ++++++++++++++++++ src/components/themed/ThemedTextInput.tsx | 63 ++++++++++++----------- 8 files changed, 182 insertions(+), 47 deletions(-) create mode 100644 src/components/PasswordInput.tsx create mode 100644 src/components/themed/CustomModal.tsx diff --git a/app.json b/app.json index 5f19c0f..f57bf5e 100644 --- a/app.json +++ b/app.json @@ -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 diff --git a/app/login.tsx b/app/login.tsx index b6c0197..cfb2884 100644 --- a/app/login.tsx +++ b/app/login.tsx @@ -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" /> - setPassword({value: text})} error={!!error && error?.id === "password"} errorText={error?.message} - secureTextEntry /> Log in diff --git a/package-lock.json b/package-lock.json index 4b108d8..cdab3cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index a3abce8..44b9ab6 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/ChangePasswordModal.tsx b/src/components/ChangePasswordModal.tsx index 5dea311..8f0c0ee 100644 --- a/src/components/ChangePasswordModal.tsx +++ b/src/components/ChangePasswordModal.tsx @@ -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 ( <> - - + Passwort ändern {apiResponse && apiResponse.message && ( @@ -60,19 +57,17 @@ export default function ChangePasswordModal() { )} - - @@ -84,7 +79,7 @@ export default function ChangePasswordModal() { - + ); } diff --git a/src/components/PasswordInput.tsx b/src/components/PasswordInput.tsx new file mode 100644 index 0000000..84b128d --- /dev/null +++ b/src/components/PasswordInput.tsx @@ -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(null); + const inputRef = useRef(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 ( + { + setText(input); + }} + onSelectionChange={(e) => { + // Cursor-Position speichern + setCursorPosition(e.nativeEvent.selection.start); + }} + right={ + { + setHidePass(!hidePass); + handleCursorReset(); // Cursor-Position zurücksetzen + }} + /> + } + /> + ); +} diff --git a/src/components/themed/CustomModal.tsx b/src/components/themed/CustomModal.tsx new file mode 100644 index 0000000..6ec08d6 --- /dev/null +++ b/src/components/themed/CustomModal.tsx @@ -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 ( + <> + + + + {children} + + + + ); +} + +const styles = StyleSheet.create({ + modalContent: { + padding: 16, + borderRadius: 8, + }, + apiResponseBox: { + marginBottom: 8, + marginTop: 8, + padding: 8, + }, + buttonGroup: { + flexDirection: "row", + justifyContent: "space-between", + marginTop: 16, + }, +}); diff --git a/src/components/themed/ThemedTextInput.tsx b/src/components/themed/ThemedTextInput.tsx index 99e03e1..676cdc5 100644 --- a/src/components/themed/ThemedTextInput.tsx +++ b/src/components/themed/ThemedTextInput.tsx @@ -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( + ({ 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 ( + + + {description && !error ? ( + {description} + ) : null} + {error && {errorText}} + + ); } +); - const errorStyle = { - fontSize: 13, - color: theme.colors.error, - paddingTop: 8, - }; - - return ( - - - {description && !error ? ( - {description} - ) : null} - {error && {errorText}} - - ); -} +export default ThemedTextInput; const styles = StyleSheet.create({ container: {