adding debounce and validation to login update

This commit is contained in:
Roland Osborne 2022-09-22 12:23:49 -07:00
parent 18716162cc
commit 4591759c2c
7 changed files with 148 additions and 29 deletions

View File

@ -39,7 +39,7 @@ export function Create() {
<View style={styles.inputwrapper}> <View style={styles.inputwrapper}>
<Ionicons style={styles.icon} name="database" size={18} color="#888888" /> <Ionicons style={styles.icon} name="database" size={18} color="#888888" />
<TextInput style={styles.inputfield} value={state.server} onChangeText={actions.setServer} <TextInput style={styles.inputfield} value={state.server} onChangeText={actions.setServer}
autoCapitalize="none" placeholder="server" /> autoCorrect={false} autoCapitalize="none" placeholder="server" />
<View style={styles.space}> <View style={styles.space}>
{ (!state.server || !state.serverChecked) && ( { (!state.server || !state.serverChecked) && (
<Text style={styles.required}></Text> <Text style={styles.required}></Text>
@ -57,7 +57,7 @@ export function Create() {
<View style={styles.inputwrapper}> <View style={styles.inputwrapper}>
<Ionicons style={styles.icon} name="key" size={18} color="#888888" /> <Ionicons style={styles.icon} name="key" size={18} color="#888888" />
<TextInput style={styles.inputfield} value={state.token} onChangeText={actions.setToken} <TextInput style={styles.inputfield} value={state.token} onChangeText={actions.setToken}
autoCapitalize="none" placeholder="token" /> autoCorrect={false} autoCapitalize="none" placeholder="token" />
<View style={styles.space}> <View style={styles.space}>
{ (!validServer || !state.token || !state.tokenChecked) && ( { (!validServer || !state.token || !state.tokenChecked) && (
<Text style={styles.required}></Text> <Text style={styles.required}></Text>
@ -75,7 +75,7 @@ export function Create() {
<View style={styles.inputwrapper}> <View style={styles.inputwrapper}>
<Ionicons style={styles.icon} name="user" size={18} color="#888888" /> <Ionicons style={styles.icon} name="user" size={18} color="#888888" />
<TextInput style={styles.inputfield} value={state.username} onChangeText={actions.setUsername} <TextInput style={styles.inputfield} value={state.username} onChangeText={actions.setUsername}
autoCapitalize="none" placeholder="username" /> autoCorrect={false} autoCapitalize="none" placeholder="username" />
<View style={styles.space}> <View style={styles.space}>
{ (!validServer || !validToken || !state.username || !state.usernameChecked) && ( { (!validServer || !validToken || !state.username || !state.usernameChecked) && (
<Text style={styles.required}></Text> <Text style={styles.required}></Text>
@ -92,7 +92,7 @@ export function Create() {
<View style={styles.inputwrapper}> <View style={styles.inputwrapper}>
<Ionicons style={styles.icon} name="lock" size={18} color="#888888" /> <Ionicons style={styles.icon} name="lock" size={18} color="#888888" />
<TextInput style={styles.inputfield} value={state.password} onChangeText={actions.setPassword} <TextInput style={styles.inputfield} value={state.password} onChangeText={actions.setPassword}
autoCapitalize="none" placeholder="password" /> autoCorrect={false} autoCapitalize="none" placeholder="password" />
<TouchableOpacity onPress={actions.hidePassword}> <TouchableOpacity onPress={actions.hidePassword}>
<Ionicons style={styles.icon} name="eye" size={18} color="#888888" /> <Ionicons style={styles.icon} name="eye" size={18} color="#888888" />
</TouchableOpacity> </TouchableOpacity>
@ -102,7 +102,7 @@ export function Create() {
<View style={styles.inputwrapper}> <View style={styles.inputwrapper}>
<Ionicons style={styles.icon} name="lock" size={18} color="#888888" /> <Ionicons style={styles.icon} name="lock" size={18} color="#888888" />
<TextInput style={styles.inputfield} value={state.password} onChangeText={actions.setPassword} <TextInput style={styles.inputfield} value={state.password} onChangeText={actions.setPassword}
secureTextEntry={true} autoCapitalize="none" placeholder="password" /> autoCorrect={false} secureTextEntry={true} autoCapitalize="none" placeholder="password" />
<TouchableOpacity onPress={actions.showPassword}> <TouchableOpacity onPress={actions.showPassword}>
<Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" /> <Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" />
</TouchableOpacity> </TouchableOpacity>
@ -112,7 +112,7 @@ export function Create() {
<View style={styles.inputwrapper}> <View style={styles.inputwrapper}>
<Ionicons style={styles.icon} name="lock" size={18} color="#888888" /> <Ionicons style={styles.icon} name="lock" size={18} color="#888888" />
<TextInput style={styles.inputfield} value={state.confirm} onChangeText={actions.setConfirm} <TextInput style={styles.inputfield} value={state.confirm} onChangeText={actions.setConfirm}
autoCapitalize="none" placeholder="confirm password" /> autoCorrect={false} autoCapitalize="none" placeholder="confirm password" />
<TouchableOpacity onPress={actions.hideConfirm}> <TouchableOpacity onPress={actions.hideConfirm}>
<Ionicons style={styles.icon} name="eye" size={18} color="#888888" /> <Ionicons style={styles.icon} name="eye" size={18} color="#888888" />
</TouchableOpacity> </TouchableOpacity>
@ -122,7 +122,7 @@ export function Create() {
<View style={styles.inputwrapper}> <View style={styles.inputwrapper}>
<Ionicons style={styles.icon} name="lock" size={18} color="#888888" /> <Ionicons style={styles.icon} name="lock" size={18} color="#888888" />
<TextInput style={styles.inputfield} value={state.confirm} onChangeText={actions.setConfirm} <TextInput style={styles.inputfield} value={state.confirm} onChangeText={actions.setConfirm}
secureTextEntry={true} autoCapitalize="none" placeholder="confirm password" /> autoCorrect={false} secureTextEntry={true} autoCapitalize="none" placeholder="confirm password" />
<TouchableOpacity onPress={actions.showConfirm}> <TouchableOpacity onPress={actions.showConfirm}>
<Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" /> <Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" />
</TouchableOpacity> </TouchableOpacity>

View File

@ -35,14 +35,14 @@ export function Login() {
<View style={styles.inputwrapper}> <View style={styles.inputwrapper}>
<Ionicons style={styles.icon} name="user" size={18} color="#aaaaaa" /> <Ionicons style={styles.icon} name="user" size={18} color="#aaaaaa" />
<TextInput style={styles.inputfield} value={state.login} onChangeText={actions.setLogin} <TextInput style={styles.inputfield} value={state.login} onChangeText={actions.setLogin}
autoCapitalize="none" placeholder="username@server" /> autoCorrect={false} autoCapitalize="none" placeholder="username@server" />
<View style={styles.space} /> <View style={styles.space} />
</View> </View>
{ state.showPassword && ( { state.showPassword && (
<View style={styles.inputwrapper}> <View style={styles.inputwrapper}>
<Ionicons style={styles.icon} name="lock" size={18} color="#aaaaaa" /> <Ionicons style={styles.icon} name="lock" size={18} color="#aaaaaa" />
<TextInput style={styles.inputfield} value={state.password} onChangeText={actions.setPassword} <TextInput style={styles.inputfield} value={state.password} onChangeText={actions.setPassword}
autoCapitalize="none" placeholder="password"/> autoCorrect={false} autoCapitalize="none" placeholder="password"/>
<TouchableOpacity onPress={actions.hidePassword}> <TouchableOpacity onPress={actions.hidePassword}>
<Ionicons style={styles.icon} name="eye" size={18} color="#aaaaaa" /> <Ionicons style={styles.icon} name="eye" size={18} color="#aaaaaa" />
</TouchableOpacity> </TouchableOpacity>
@ -52,7 +52,7 @@ export function Login() {
<View style={styles.inputwrapper}> <View style={styles.inputwrapper}>
<Ionicons style={styles.icon} name="lock" size={18} color="#aaaaaa" /> <Ionicons style={styles.icon} name="lock" size={18} color="#aaaaaa" />
<TextInput style={styles.inputfield} value={state.password} onChangeText={actions.setPassword} <TextInput style={styles.inputfield} value={state.password} onChangeText={actions.setPassword}
secureTextEntry={true} autoCapitalize="none" placeholder="password" /> autoCorrect={false} secureTextEntry={true} autoCapitalize="none" placeholder="password" />
<TouchableOpacity onPress={actions.showPassword}> <TouchableOpacity onPress={actions.showPassword}>
<Ionicons style={styles.icon} name="eyeo" size={18} color="#aaaaaa" /> <Ionicons style={styles.icon} name="eyeo" size={18} color="#aaaaaa" />
</TouchableOpacity> </TouchableOpacity>

View File

@ -0,0 +1,8 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function getHandle(server, token, name) {
let available = await fetchWithTimeout(`https://${server}/account/username?agent=${token}&name=${encodeURIComponent(name)}`, { method: 'GET' })
checkResponse(available)
return await available.json()
}

View File

@ -3,6 +3,7 @@ import { getProfile } from 'api/getProfile';
import { setProfileData } from 'api/setProfileData'; import { setProfileData } from 'api/setProfileData';
import { setProfileImage } from 'api/setProfileImage'; import { setProfileImage } from 'api/setProfileImage';
import { getProfileImageUrl } from 'api/getProfileImageUrl'; import { getProfileImageUrl } from 'api/getProfileImageUrl';
import { getHandle } from 'api/getHandle';
import { StoreContext } from 'context/StoreContext'; import { StoreContext } from 'context/StoreContext';
export function useProfileContext() { export function useProfileContext() {
@ -71,6 +72,10 @@ export function useProfileContext() {
const { server, appToken } = session.current; const { server, appToken } = session.current;
await setProfileImage(server, appToken, image); await setProfileImage(server, appToken, image);
}, },
getHandle: async (name) => {
const { server, appToken } = session.current;
return await getHandle(server, appToken, name);
},
} }
return { state, actions } return { state, actions }

View File

@ -39,6 +39,17 @@ export function Profile() {
} }
const saveLogin = async () => { const saveLogin = async () => {
try {
await actions.saveLogin();
actions.hideLoginEdit();
}
catch (err) {
console.log(err);
Alert.alert(
'Failed to Change Login',
'Please try again.'
)
}
} }
const onGallery = async () => { const onGallery = async () => {
@ -63,6 +74,8 @@ export function Profile() {
} }
} }
const enabled = (state.checked && state.available && state.editConfirm === state.editPassword && state.editPassword);
return ( return (
<ScrollView> <ScrollView>
<View style={styles.container}> <View style={styles.container}>
@ -113,15 +126,15 @@ export function Profile() {
<Text style={styles.editHeader}>Edit Details:</Text> <Text style={styles.editHeader}>Edit Details:</Text>
<View style={styles.inputField}> <View style={styles.inputField}>
<TextInput style={styles.input} value={state.editName} onChangeText={actions.setEditName} <TextInput style={styles.input} value={state.editName} onChangeText={actions.setEditName}
autoCapitalize="word" placeholder="Name" /> autoCapitalize="words" placeholder="Name" />
</View> </View>
<View style={styles.inputField}> <View style={styles.inputField}>
<TextInput style={styles.input} value={state.editLocation} onChangeText={actions.setEditLocation} <TextInput style={styles.input} value={state.editLocation} onChangeText={actions.setEditLocation}
autoCapitalize="sentence" placeholder="Location" /> autoCapitalize="words" placeholder="Location" />
</View> </View>
<View style={styles.inputField}> <View style={styles.inputField}>
<TextInput style={styles.input} value={state.editDescription} onChangeText={actions.setEditDescription} <TextInput style={styles.input} value={state.editDescription} onChangeText={actions.setEditDescription}
autoCapitalize="none" placeholder="Description" multiline={true} /> autoCapitalize="sentences" placeholder="Description" multiline={true} />
</View> </View>
<View style={styles.editControls}> <View style={styles.editControls}>
<TouchableOpacity style={styles.cancel} onPress={actions.hideDetailEdit}> <TouchableOpacity style={styles.cancel} onPress={actions.hideDetailEdit}>
@ -146,23 +159,64 @@ export function Profile() {
<Text style={styles.editHeader}>Change Login:</Text> <Text style={styles.editHeader}>Change Login:</Text>
<View style={styles.inputField}> <View style={styles.inputField}>
<TextInput style={styles.input} value={state.editHandle} onChangeText={actions.setEditHandle} <TextInput style={styles.input} value={state.editHandle} onChangeText={actions.setEditHandle}
placeholder="Username" /> autoCapitalize={'none'} placeholder="Username" />
</View> { state.checked && state.available && (
<View style={styles.inputField}> <Ionicons style={styles.icon} name="checkcircleo" size={18} color={Colors.background} />
<TextInput style={styles.input} value={state.editPassword} onChangeText={actions.setEditPassword} )}
secureTextEntry={true} placeholder="Password" /> { state.checked && !state.available && (
</View> <Ionicons style={styles.icon} name="exclamationcircleo" size={18} color={Colors.alert} />
<View style={styles.inputField}> )}
<TextInput style={styles.input} value={state.editConfirm} onChangeText={actions.setEditConfirm}
secureTextEntry={true} placeholder="Confirm Password" />
</View> </View>
{ !state.showPassword && (
<View style={styles.inputField}>
<TextInput style={styles.input} value={state.editPassword} onChangeText={actions.setEditPassword}
autoCapitalize={'none'} secureTextEntry={true} placeholder="Password" />
<TouchableOpacity onPress={actions.showPassword}>
<Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" />
</TouchableOpacity>
</View>
)}
{ state.showPassword && (
<View style={styles.inputField}>
<TextInput style={styles.input} value={state.editPassword} onChangeText={actions.setEditPassword}
autoCapitalize={'none'} secureTextEntry={false} placeholder="Password" />
<TouchableOpacity onPress={actions.hidePassword}>
<Ionicons style={styles.icon} name="eye" size={18} color="#888888" />
</TouchableOpacity>
</View>
)}
{ !state.showConfirm && (
<View style={styles.inputField}>
<TextInput style={styles.input} value={state.editConfirm} onChangeText={actions.setEditConfirm}
autoCapitalize={'none'} secureTextEntry={true} placeholder="Confirm" />
<TouchableOpacity onPress={actions.showConfirm}>
<Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" />
</TouchableOpacity>
</View>
)}
{ state.showConfirm && (
<View style={styles.inputField}>
<TextInput style={styles.input} value={state.editConfirm} onChangeText={actions.setEditConfirm}
autoCapitalize={'none'} secureTextEntry={false} placeholder="Confirm" />
<TouchableOpacity onPress={actions.hideConfirm}>
<Ionicons style={styles.icon} name="eye" size={18} color="#888888" />
</TouchableOpacity>
</View>
)}
<View style={styles.editControls}> <View style={styles.editControls}>
<TouchableOpacity style={styles.cancel} onPress={actions.hideLoginEdit}> <TouchableOpacity style={styles.cancel} onPress={actions.hideLoginEdit}>
<Text>Cancel</Text> <Text>Cancel</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity style={styles.save} onPress={saveLogin}> { enabled && (
<Text style={styles.saveText}>Save</Text> <TouchableOpacity style={styles.save} onPress={saveLogin}>
</TouchableOpacity> <Text style={styles.saveText}>Save</Text>
</TouchableOpacity>
)}
{ !enabled && (
<View style={styles.disabled}>
<Text style={styles.disabledText}>Save</Text>
</View>
)}
</View> </View>
</View> </View>
</View> </View>

View File

@ -128,10 +128,12 @@ export const styles = StyleSheet.create({
padding: 8, padding: 8,
marginBottom: 8, marginBottom: 8,
maxHeight: 92, maxHeight: 92,
display: 'flex',
flexDirection: 'row',
}, },
input: { input: {
fontSize: 16, fontSize: 16,
width: '100%', flexGrow: 1,
}, },
editControls: { editControls: {
display: 'flex', display: 'flex',
@ -148,6 +150,18 @@ export const styles = StyleSheet.create({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
}, },
disabled: {
borderWidth: 1,
borderColor: Colors.lightgrey,
padding: 8,
borderRadius: 4,
width: 72,
display: 'flex',
alignItems: 'center',
},
disabledText: {
color: Colors.disabled,
},
save: { save: {
padding: 8, padding: 8,
borderRadius: 4, borderRadius: 4,

View File

@ -22,12 +22,17 @@ export function useProfile() {
editHandle: null, editHandle: null,
editPassword: null, editPassword: null,
editConfirm: null, editConfirm: null,
checked: true,
available: true,
showPassword: false,
showConfirm: false,
}); });
const app = useContext(AppContext); const app = useContext(AppContext);
const account = useContext(AccountContext); const account = useContext(AccountContext);
const profile = useContext(ProfileContext); const profile = useContext(ProfileContext);
const navigate = useNavigate(); const navigate = useNavigate();
const debounce = useRef(null);
const updateState = (value) => { const updateState = (value) => {
setState((s) => ({ ...s, ...value })); setState((s) => ({ ...s, ...value }));
@ -76,8 +81,38 @@ export function useProfile() {
setEditDescription: (editDescription) => { setEditDescription: (editDescription) => {
updateState({ editDescription }); updateState({ editDescription });
}, },
showPassword: () => {
updateState({ showPassword: true });
},
hidePassword: () => {
updateState({ showPassword: false });
},
showConfirm: () => {
updateState({ showConfirm: true });
},
hideConfirm: () => {
updateState({ showConfirm: false });
},
setEditHandle: (editHandle) => { setEditHandle: (editHandle) => {
updateState({ editHandle }); updateState({ editHandle, checked: false });
if (debounce.current != null) {
clearTimeout(debounce.current);
}
debounce.current = setTimeout(async () => {
try {
if (editHandle === state.handle) {
updateState({ available: true, checked: true });
}
else {
const available = await profile.actions.getHandle(editHandle);
updateState({ available, checked: true });
}
}
catch (err) {
console.log(err);
}
}, 1000);
}, },
setEditPassword: (editPassword) => { setEditPassword: (editPassword) => {
updateState({ editPassword }); updateState({ editPassword });
@ -85,8 +120,11 @@ export function useProfile() {
setEditConfirm: (editConfirm) => { setEditConfirm: (editConfirm) => {
updateState({ editConfirm }); updateState({ editConfirm });
}, },
saveDetails: () => { saveDetails: async () => {
profile.actions.setProfileData(state.editName, state.editLocation, state.editDescription); await profile.actions.setProfileData(state.editName, state.editLocation, state.editDescription);
},
saveLogin: async () => {
await account.actions.setLogin(state.editHandle, state.editPassword);
}, },
}; };