mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-03-23 09:46:27 +00:00
refactor: replace inline password inputs with reusable PasswordInput component
Consolidates duplicate password input implementations across login and settings screens into a single reusable component. Improves code maintainability by eliminating redundant password visibility toggle logic and standardizing password input behavior throughout the application. Adds consistent accessibility support and test identifiers across all password input instances.
This commit is contained in:
101
components/PasswordInput.tsx
Normal file
101
components/PasswordInput.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import { Input } from "./common/Input";
|
||||
|
||||
interface PasswordInputProps {
|
||||
value?: string;
|
||||
onChangeText: (text: string) => void;
|
||||
onSubmitEditing?: () => void;
|
||||
placeholder: string;
|
||||
className?: string;
|
||||
testID?: string;
|
||||
accessibilityLabel?: string;
|
||||
maxLength?: number;
|
||||
showPassword?: boolean;
|
||||
onShowPasswordChange?: (show: boolean) => void;
|
||||
topPosition?: "3.5" | "4";
|
||||
editable?: boolean;
|
||||
autoComplete?:
|
||||
| "password"
|
||||
| "username"
|
||||
| "name"
|
||||
| "email"
|
||||
| "tel"
|
||||
| "url"
|
||||
| "off";
|
||||
autoCorrect?: boolean;
|
||||
}
|
||||
|
||||
export const PasswordInput: React.FC<PasswordInputProps> = ({
|
||||
value = "",
|
||||
onChangeText,
|
||||
onSubmitEditing,
|
||||
placeholder,
|
||||
className = "",
|
||||
testID,
|
||||
accessibilityLabel,
|
||||
maxLength = 500,
|
||||
showPassword: controlledShowPassword,
|
||||
onShowPasswordChange,
|
||||
topPosition = "3.5",
|
||||
editable = true,
|
||||
autoComplete,
|
||||
autoCorrect,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [internalShowPassword, setInternalShowPassword] = useState(false);
|
||||
|
||||
// Use controlled state if provided, otherwise use internal state
|
||||
const showPassword = controlledShowPassword ?? internalShowPassword;
|
||||
const setShowPassword = onShowPasswordChange ?? setInternalShowPassword;
|
||||
|
||||
const topClass = topPosition === "4" ? "top-4" : "top-3.5";
|
||||
|
||||
return (
|
||||
<View className={`relative ${className}`}>
|
||||
<Input
|
||||
placeholder={placeholder}
|
||||
onChangeText={onChangeText}
|
||||
value={value}
|
||||
secureTextEntry={!showPassword}
|
||||
keyboardType='default'
|
||||
returnKeyType='done'
|
||||
autoCapitalize='none'
|
||||
textContentType='password'
|
||||
clearButtonMode='while-editing'
|
||||
maxLength={maxLength}
|
||||
className='pr-12'
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
testID={testID}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
editable={editable}
|
||||
autoComplete={autoComplete}
|
||||
autoCorrect={autoCorrect}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowPassword(!showPassword)}
|
||||
className={`absolute right-3 ${topClass} p-1`}
|
||||
accessible={true}
|
||||
accessibilityRole='button'
|
||||
accessibilityLabel={
|
||||
showPassword
|
||||
? t("accessibility.hide_password")
|
||||
: t("accessibility.show_password")
|
||||
}
|
||||
accessibilityHint={t("accessibility.toggle_password_visibility")}
|
||||
accessibilityState={{ selected: showPassword }}
|
||||
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||
testID={testID ? `${testID}-toggle` : undefined}
|
||||
>
|
||||
<Ionicons
|
||||
name={showPassword ? "eye-off" : "eye"}
|
||||
size={24}
|
||||
color='white'
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useAtom } from "jotai";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import { View } from "react-native";
|
||||
import { toast } from "sonner-native";
|
||||
import { PasswordInput } from "@/components/PasswordInput";
|
||||
import { JellyseerrApi, useJellyseerr } from "@/hooks/useJellyseerr";
|
||||
import { userAtom } from "@/providers/JellyfinProvider";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
@@ -150,37 +150,24 @@ export const JellyseerrSettings = () => {
|
||||
<Text className='font-bold mb-2'>
|
||||
{t("home.settings.plugins.jellyseerr.password")}
|
||||
</Text>
|
||||
<View className='relative'>
|
||||
<Input
|
||||
className='border border-neutral-800 pr-12'
|
||||
autoFocus={true}
|
||||
focusable={true}
|
||||
placeholder={t(
|
||||
"home.settings.plugins.jellyseerr.password_placeholder",
|
||||
{ username: user?.Name },
|
||||
)}
|
||||
value={jellyseerrPassword}
|
||||
keyboardType='default'
|
||||
secureTextEntry={!showJellyseerrPassword}
|
||||
returnKeyType='done'
|
||||
autoCapitalize='none'
|
||||
textContentType='password'
|
||||
onChangeText={setJellyseerrPassword}
|
||||
editable={!loginToJellyseerrMutation.isPending}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={() =>
|
||||
setShowJellyseerrPassword(!showJellyseerrPassword)
|
||||
}
|
||||
className='absolute right-3 top-3.5 p-1'
|
||||
>
|
||||
<Ionicons
|
||||
name={showJellyseerrPassword ? "eye-off" : "eye"}
|
||||
size={24}
|
||||
color='white'
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<PasswordInput
|
||||
value={jellyseerrPassword}
|
||||
onChangeText={setJellyseerrPassword}
|
||||
placeholder={t(
|
||||
"home.settings.plugins.jellyseerr.password_placeholder",
|
||||
{ username: user?.Name },
|
||||
)}
|
||||
className='border border-neutral-800'
|
||||
showPassword={showJellyseerrPassword}
|
||||
onShowPasswordChange={setShowJellyseerrPassword}
|
||||
editable={!loginToJellyseerrMutation.isPending}
|
||||
autoComplete='password'
|
||||
autoCorrect={false}
|
||||
testID='jellyseerr-password-input'
|
||||
accessibilityLabel={t(
|
||||
"home.settings.plugins.jellyseerr.password",
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
loading={loginToJellyseerrMutation.isPending}
|
||||
disabled={loginToJellyseerrMutation.isPending}
|
||||
|
||||
Reference in New Issue
Block a user