Merge branch 'main' into feature/adc/retour-utilisateur
This commit is contained in:
+2
-1
@@ -23,6 +23,7 @@ import { AccessTokenResponseHandler } from './models/AccessTokenResponse';
|
||||
import * as yup from 'yup';
|
||||
import { base64ToBlob } from './utils/base64ToBlob';
|
||||
import { ImagePickerAsset } from 'expo-image-picker';
|
||||
import Constant from 'expo-constants';
|
||||
|
||||
type AuthenticationInput = { username: string; password: string };
|
||||
type RegistrationInput = AuthenticationInput & { email: string };
|
||||
@@ -68,7 +69,7 @@ export default class API {
|
||||
public static readonly baseUrl =
|
||||
process.env.NODE_ENV != 'development' && Platform.OS === 'web'
|
||||
? '/api'
|
||||
: 'https://nightly.chroma.octohub.app/api';
|
||||
: Constant.manifest?.extra?.apiUrl;
|
||||
public static async fetch(
|
||||
params: FetchParams,
|
||||
handle: Pick<Required<HandleParams>, 'raw'>
|
||||
|
||||
@@ -33,6 +33,8 @@ import VerifiedView from './views/VerifiedView';
|
||||
import SigninView from './views/SigninView';
|
||||
import SignupView from './views/SignupView';
|
||||
import TabNavigation from './components/V2/TabNavigation';
|
||||
import PasswordResetView from './views/PasswordResetView';
|
||||
import ForgotPasswordView from './views/ForgotPasswordView';
|
||||
|
||||
// Util function to hide route props in URL
|
||||
const removeMe = () => '';
|
||||
@@ -123,6 +125,16 @@ const publicRoutes = () =>
|
||||
options: { title: 'Google signin', headerShown: false },
|
||||
link: '/logged/google',
|
||||
},
|
||||
PasswordReset: {
|
||||
component: PasswordResetView,
|
||||
options: { title: 'Password reset form', headerShown: false },
|
||||
link: '/password_reset',
|
||||
},
|
||||
ForgotPassword: {
|
||||
component: ForgotPasswordView,
|
||||
options: { title: 'Password reset form', headerShown: false },
|
||||
link: '/forgot_password',
|
||||
},
|
||||
} as const);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
import { translate } from '../../i18n/i18n';
|
||||
import { string } from 'yup';
|
||||
import { FormControl, Input, Stack, WarningOutlineIcon, Box, Button, useToast } from 'native-base';
|
||||
|
||||
interface ForgotPasswordFormProps {
|
||||
onSubmit: (email: string) => Promise<string>;
|
||||
}
|
||||
|
||||
const validationSchemas = {
|
||||
email: string().email('Invalid email').required('Email is required'),
|
||||
};
|
||||
|
||||
const ForgotPasswordForm = ({ onSubmit }: ForgotPasswordFormProps) => {
|
||||
const [formData, setFormData] = React.useState({
|
||||
newEmail: {
|
||||
value: '',
|
||||
error: null as string | null,
|
||||
},
|
||||
});
|
||||
|
||||
const [submittingForm, setSubmittingForm] = React.useState(false);
|
||||
const toast = useToast();
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack mx="4" style={{ width: '80%', maxWidth: 400 }}>
|
||||
<FormControl isRequired isInvalid={formData.newEmail.error !== null}>
|
||||
<FormControl.Label>{translate('newEmail')}</FormControl.Label>
|
||||
<Input
|
||||
isRequired
|
||||
type="text"
|
||||
placeholder={translate('newEmail')}
|
||||
value={formData.newEmail.value}
|
||||
onChangeText={(t) => {
|
||||
let error: null | string = null;
|
||||
validationSchemas.email
|
||||
.validate(t)
|
||||
.catch((e) => (error = e.message))
|
||||
.finally(() => {
|
||||
setFormData({ ...formData, newEmail: { value: t, error } });
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
||||
{formData.newEmail.error}
|
||||
</FormControl.ErrorMessage>
|
||||
|
||||
<Button
|
||||
style={{ marginTop: 10 }}
|
||||
isLoading={submittingForm}
|
||||
isDisabled={formData.newEmail.error !== null}
|
||||
onPress={async () => {
|
||||
setSubmittingForm(true);
|
||||
try {
|
||||
const resp = await onSubmit(formData.newEmail.value);
|
||||
toast.show({ description: resp });
|
||||
} catch (e) {
|
||||
toast.show({ description: e as string });
|
||||
} finally {
|
||||
setSubmittingForm(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{translate('submitBtn')}
|
||||
</Button>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForgotPasswordForm;
|
||||
@@ -0,0 +1,114 @@
|
||||
import React from 'react';
|
||||
import { translate } from '../../i18n/i18n';
|
||||
import { string } from 'yup';
|
||||
import { FormControl, Input, Stack, WarningOutlineIcon, Box, Button, useToast } from 'native-base';
|
||||
|
||||
interface PasswordResetFormProps {
|
||||
onSubmit: (newPassword: string) => Promise<string>;
|
||||
}
|
||||
|
||||
const PasswordResetForm = ({ onSubmit }: PasswordResetFormProps) => {
|
||||
const [formData, setFormData] = React.useState({
|
||||
newPassword: {
|
||||
value: '',
|
||||
error: null as string | null,
|
||||
},
|
||||
confirmNewPassword: {
|
||||
value: '',
|
||||
error: null as string | null,
|
||||
},
|
||||
});
|
||||
const [submittingForm, setSubmittingForm] = React.useState(false);
|
||||
|
||||
const validationSchemas = {
|
||||
password: string()
|
||||
.min(4, translate('passwordTooShort'))
|
||||
.max(100, translate('passwordTooLong'))
|
||||
.required('Password is required'),
|
||||
};
|
||||
const toast = useToast();
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack mx="4" style={{ width: '80%', maxWidth: 400 }}>
|
||||
<FormControl
|
||||
isRequired
|
||||
isInvalid={
|
||||
formData.newPassword.error !== null ||
|
||||
formData.confirmNewPassword.error !== null
|
||||
}
|
||||
>
|
||||
<FormControl.Label>{translate('newPassword')}</FormControl.Label>
|
||||
<Input
|
||||
isRequired
|
||||
type="password"
|
||||
placeholder={translate('newPassword')}
|
||||
value={formData.newPassword.value}
|
||||
onChangeText={(t) => {
|
||||
let error: null | string = null;
|
||||
validationSchemas.password
|
||||
.validate(t)
|
||||
.catch((e) => (error = e.message))
|
||||
.finally(() => {
|
||||
setFormData({ ...formData, newPassword: { value: t, error } });
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
||||
{formData.newPassword.error}
|
||||
</FormControl.ErrorMessage>
|
||||
|
||||
<FormControl.Label>{translate('confirmNewPassword')}</FormControl.Label>
|
||||
<Input
|
||||
isRequired
|
||||
type="password"
|
||||
placeholder={translate('confirmNewPassword')}
|
||||
value={formData.confirmNewPassword.value}
|
||||
onChangeText={(t) => {
|
||||
let error: null | string = null;
|
||||
validationSchemas.password
|
||||
.validate(t)
|
||||
.catch((e) => (error = e.message));
|
||||
if (!error && t !== formData.newPassword.value) {
|
||||
error = translate('passwordsDontMatch');
|
||||
}
|
||||
setFormData({
|
||||
...formData,
|
||||
confirmNewPassword: { value: t, error },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
||||
{formData.confirmNewPassword.error}
|
||||
</FormControl.ErrorMessage>
|
||||
|
||||
<Button
|
||||
style={{ marginTop: 10 }}
|
||||
isLoading={submittingForm}
|
||||
isDisabled={
|
||||
formData.newPassword.error !== null ||
|
||||
formData.confirmNewPassword.error !== null ||
|
||||
formData.newPassword.value === '' ||
|
||||
formData.confirmNewPassword.value === ''
|
||||
}
|
||||
onPress={async () => {
|
||||
setSubmittingForm(true);
|
||||
try {
|
||||
const resp = await onSubmit(formData.newPassword.value);
|
||||
toast.show({ description: resp });
|
||||
} catch (e) {
|
||||
toast.show({ description: e as string });
|
||||
} finally {
|
||||
setSubmittingForm(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{translate('submitBtn')}
|
||||
</Button>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default PasswordResetForm;
|
||||
+3
-3
@@ -54,7 +54,7 @@
|
||||
"native-base": "^3.4.17",
|
||||
"opensheetmusicdisplay": "^1.7.5",
|
||||
"phaser": "^3.60.0",
|
||||
"react": "18.1.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.1.0",
|
||||
"react-i18next": "^11.18.3",
|
||||
"react-native": "0.70.5",
|
||||
@@ -91,8 +91,8 @@
|
||||
"@storybook/testing-library": "^0.0.13",
|
||||
"@testing-library/react-native": "^11.0.0",
|
||||
"@types/node": "^18.11.8",
|
||||
"@types/react": "~18.0.24",
|
||||
"@types/react-native": "~0.70.6",
|
||||
"@types/react": "~18.2.0",
|
||||
"@types/react-native": "~0.70.5",
|
||||
"@types/react-navigation": "^3.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.43.0",
|
||||
"@typescript-eslint/parser": "^5.0.0",
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import API from '../API';
|
||||
import { useNavigation } from '../Navigation';
|
||||
import ForgotPasswordForm from '../components/forms/forgotPasswordForm';
|
||||
|
||||
const ForgotPasswordView = () => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
async function handleSubmit(email: string) {
|
||||
try {
|
||||
await API.fetch({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
route: `/auth/forgot-password?email=${email}`,
|
||||
method: 'PUT',
|
||||
});
|
||||
navigation.navigate('Home');
|
||||
return 'email sent';
|
||||
} catch {
|
||||
return 'Error with email, please contact support';
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<ForgotPasswordForm onSubmit={handleSubmit} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForgotPasswordView;
|
||||
@@ -0,0 +1,34 @@
|
||||
import API from '../API';
|
||||
import { useNavigation } from '../Navigation';
|
||||
import { useRoute } from '@react-navigation/native';
|
||||
import PasswordResetForm from '../components/forms/passwordResetForm';
|
||||
|
||||
const PasswordResetView = () => {
|
||||
const navigation = useNavigation();
|
||||
const route = useRoute();
|
||||
|
||||
const handlePasswordReset = async (password: string) => {
|
||||
try {
|
||||
await API.fetch({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
route: `/auth/password-reset?token=${(route.params as any).token}`,
|
||||
method: 'PUT',
|
||||
body: {
|
||||
password,
|
||||
},
|
||||
});
|
||||
navigation.navigate('Home');
|
||||
return 'password succesfully reset';
|
||||
} catch {
|
||||
return 'password reset failed';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PasswordResetForm onSubmit={(password) => handlePasswordReset(password)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PasswordResetView;
|
||||
@@ -100,7 +100,7 @@ const SigninView = () => {
|
||||
}}
|
||||
isRequired
|
||||
/>,
|
||||
<LinkBase key={'signin-link'} onPress={() => console.log('Link clicked!')}>
|
||||
<LinkBase key={'signin-link'} onPress={() => navigation.navigate('ForgotPassword')}>
|
||||
{translate('forgottenPassword')}
|
||||
</LinkBase>,
|
||||
]}
|
||||
|
||||
@@ -178,8 +178,8 @@ const SignupView = () => {
|
||||
try {
|
||||
const resp = await onSubmit(
|
||||
formData.username.value,
|
||||
formData.password.value,
|
||||
formData.email.value
|
||||
formData.email.value,
|
||||
formData.password.value
|
||||
);
|
||||
toast.show({ description: resp, colorScheme: 'secondary' });
|
||||
} catch (e) {
|
||||
|
||||
@@ -178,6 +178,23 @@ const StartPageView = () => {
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
style={{
|
||||
width: '90%',
|
||||
marginTop: 20,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Link href="/forgot_password">I forgot my password</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Column>
|
||||
</View>
|
||||
);
|
||||
|
||||
+3384
-3662
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user