Feature/addsignupsignin (#83)

* first branch commit

* testing formik yup in react native

* made TextInput work with formik

* react native base form control working great removed formik

* adding the button logic

* fix stupid error when merging this commit and precending ones need to be rebased

* fix multiple merging issues

* functionnal login form setup in the app

* added translation support for LoginForm

* added a forgotten password and added translation fixes

* Renamed loginform to Signinform for better coherence

* v1 of signup form and finished a missing renamed for signinform

* errors messages are now displayed correctly

* removed unused helper text

* addded translations

* added a password complexity check

* added missing translations and a temporary implementation of the AuthentificationWiew

* PR diff preview quick fixes: Removed unused imports, auto format, removed unnecessary changes

* Front: Authentication View: Use toast as helper messages

* first branch commit

* testing formik yup in react native

* made TextInput work with formik

* react native base form control working great removed formik

* adding the button logic

* fix stupid error when merging this commit and precending ones need to be rebased

* fix multiple merging issues

* functionnal login form setup in the app

* added translation support for LoginForm

* added a forgotten password and added translation fixes

* Renamed loginform to Signinform for better coherence

* v1 of signup form and finished a missing renamed for signinform

* errors messages are now displayed correctly

* removed unused helper text

* addded translations

* added a password complexity check

* added missing translations and a temporary implementation of the AuthentificationWiew

* PR diff preview quick fixes: Removed unused imports, auto format, removed unnecessary changes

* Front: Authentication View: Use toast as helper messages

* removed a worng declaration

* fixed PR comments

Co-authored-by: Arthi-chaud <arthur.jamet@gmail.com>
This commit is contained in:
Clément Le Bihan
2022-10-23 08:49:49 +01:00
committed by GitHub
parent 0d17119cf4
commit 30d3be72b2
11 changed files with 859 additions and 366 deletions
+1
View File
@@ -9,3 +9,4 @@ prisma/migrations/*
output.xml
report.html
log.html
.expo
+30
View File
@@ -5,6 +5,7 @@ import LessonHistory from "./models/LessonHistory";
import Song from "./models/Song";
import SongHistory from "./models/SongHistory";
import User from "./models/User";
import { translate } from "./i18n/i18n";
const delay = (seconds: number) => new Promise(resolve => setTimeout(resolve, seconds * 1000));
@@ -134,4 +135,33 @@ export default class API {
userId: 1
}];
}
/**
* Get the login information status
*
*/
static async checkSigninCredentials(username: string, password: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
setTimeout(() => {
if (username === "katerina" && password === "1234") {
return resolve("token signin");
}
return reject(translate("invalidCredentials"));
}, 1000);
});
};
/**
* Get the register information status
*/
static async checkSignupCredentials(username: string, password: string, email: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
setTimeout(() => {
if (username === "bluub") {
return reject(translate("usernameTaken"));
}
return resolve("token signup");
}, 1000);
});
}
}
+1 -1
View File
@@ -29,4 +29,4 @@ export const Router = () => {
</Stack.Navigator>
</NavigationContainer>
)
}
}
-1
View File
@@ -4,5 +4,4 @@ const LoadingComponent = () => {
const theme = useTheme();
return <Spinner color={theme.colors.primary[500]}/>
}
export default LoadingComponent;
+128
View File
@@ -0,0 +1,128 @@
// a form for sign in
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 SigninFormProps {
onSubmit: (username: string, password: string) => Promise<string>;
}
const LoginForm = ({ onSubmit }: SigninFormProps) => {
const [formData, setFormData] = React.useState({
username: {
value: "",
error: null as string | null,
},
password: {
value: "",
error: null as string | null,
},
});
const [submittingForm, setSubmittingForm] = React.useState(false);
const validationSchemas = {
username: string()
.min(3, translate("usernameTooShort"))
.max(20, translate("usernameTooLong"))
.required("Username is required"),
password: string()
.min(4, translate("passwordTooShort"))
.max(100, translate("passwordTooLong"))
.required("Password is required"),
};
const toast = useToast();
return (
<Box alignItems="center" style={{ width: '100%' }}>
<Stack mx="4" style={{ width: '80%', maxWidth: 400 }}>
<FormControl
isRequired
isInvalid={
formData.username.error !== null ||
formData.password.error !== null
}
>
<FormControl.Label>{translate("username")}</FormControl.Label>
<Input
isRequired
type="text"
placeholder="Username"
value={formData.username.value}
onChangeText={(t) => {
let error: null | string = null;
validationSchemas.username
.validate(t)
.catch((e) => (error = e.message))
.finally(() => {
setFormData({ ...formData, username: { value: t, error } });
});
}}
/>
<FormControl.ErrorMessage
leftIcon={<WarningOutlineIcon size="xs" />}
>
{formData.username.error}
</FormControl.ErrorMessage>
<FormControl.Label>{translate("password")}</FormControl.Label>
<Input
isRequired
type="password"
placeholder="password"
value={formData.password.value}
onChangeText={(t) => {
let error: null | string = null;
validationSchemas.password
.validate(t)
.catch((e) => (error = e.message))
.finally(() => {
setFormData({ ...formData, password: { value: t, error } });
});
}}
/>
<FormControl.ErrorMessage
leftIcon={<WarningOutlineIcon size="xs" />}
>
{formData.password.error}
</FormControl.ErrorMessage>
<Button
style={{ marginTop: 10 }}
isLoading={submittingForm}
isDisabled={
formData.password.error !== null ||
formData.username.error !== null ||
formData.username.value === "" ||
formData.password.value === ""
}
onPress={async () => {
setSubmittingForm(true);
try {
const resp = await onSubmit(
formData.username.value,
formData.password.value
);
toast.show({ description: resp, colorScheme: 'secondary' })
} catch (e) {
toast.show({ description: e as string, colorScheme: 'red', avoidKeyboard: true })
} finally {
setSubmittingForm(false);
}
}}
>
{translate("login")}
</Button>
</FormControl>
</Stack>
</Box>
);
};
export default LoginForm;
+205
View File
@@ -0,0 +1,205 @@
// a form for sign up
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 SignupFormProps {
onSubmit: (
username: string,
password: string,
email: string
) => Promise<string>;
}
const LoginForm = ({ onSubmit }: SignupFormProps) => {
const [formData, setFormData] = React.useState({
username: {
value: "",
error: null as string | null,
},
password: {
value: "",
error: null as string | null,
},
repeatPassword: {
value: "",
error: null as string | null,
},
email: {
value: "",
error: null as string | null,
},
});
const [submittingForm, setSubmittingForm] = React.useState(false);
const validationSchemas = {
username: string()
.min(3, translate("usernameTooShort"))
.max(20, translate("usernameTooLong"))
.required("Username is required"),
email: string().email("Invalid email").required("Email is required"),
password: string()
.min(4, translate("passwordTooShort"))
.max(100, translate("passwordTooLong"))
.matches(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])(?=.{8,})/,
translate(
"Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character"
)
)
.required("Password is required"),
};
const toast = useToast();
return (
<Box alignItems="center" style={{ width: '100%' }}>
<Box style={{ width: '80%', maxWidth: 400 }}>
<Stack mx="4">
<FormControl
isRequired
isInvalid={
formData.username.error !== null ||
formData.password.error !== null ||
formData.repeatPassword.error !== null ||
formData.email.error !== null
}
>
<FormControl.Label>{translate("username")}</FormControl.Label>
<Input
isRequired
type="text"
placeholder="Katerina"
value={formData.username.value}
onChangeText={(t) => {
let error: null | string = null;
validationSchemas.username
.validate(t)
.catch((e) => (error = e.message))
.finally(() => {
setFormData({ ...formData, username: { value: t, error } });
});
}}
/>
<FormControl.ErrorMessage
leftIcon={<WarningOutlineIcon size="xs" />}
>
{formData.username.error}
</FormControl.ErrorMessage>
<FormControl.Label>{translate("email")}</FormControl.Label>
<Input
isRequired
type="text"
placeholder="lucy@er.com"
value={formData.email.value}
onChangeText={(t) => {
let error: null | string = null;
validationSchemas.email
.validate(t)
.catch((e) => (error = e.message))
.finally(() => {
setFormData({ ...formData, email: { value: t, error } });
});
}}
/>
<FormControl.ErrorMessage
leftIcon={<WarningOutlineIcon size="xs" />}
>
{formData.email.error}
</FormControl.ErrorMessage>
<FormControl.Label>{translate("password")}</FormControl.Label>
<Input
isRequired
type="password"
placeholder="password"
value={formData.password.value}
onChangeText={(t) => {
let error: null | string = null;
validationSchemas.password
.validate(t)
.catch((e) => (error = e.message))
.finally(() => {
setFormData({ ...formData, password: { value: t, error } });
});
}}
/>
<FormControl.ErrorMessage
leftIcon={<WarningOutlineIcon size="xs" />}
>
{formData.password.error}
</FormControl.ErrorMessage>
<FormControl.Label>{translate("repeatPassword")}</FormControl.Label>
<Input
isRequired
type="password"
placeholder="password"
value={formData.repeatPassword.value}
onChangeText={(t) => {
let error: null | string = null;
validationSchemas.password
.validate(t)
.catch((e) => (error = e.message))
.finally(() => {
if (!error && t !== formData.password.value) {
error = translate("passwordsDontMatch");
}
setFormData({
...formData,
repeatPassword: { value: t, error },
});
});
}}
/>
<FormControl.ErrorMessage
leftIcon={<WarningOutlineIcon size="xs" />}
>
{formData.repeatPassword.error}
</FormControl.ErrorMessage>
<Button
style={{ marginTop: 10 }}
isLoading={submittingForm}
isDisabled={
formData.password.error !== null ||
formData.username.error !== null ||
formData.repeatPassword.error !== null ||
formData.email.error !== null ||
formData.username.value === "" ||
formData.password.value === "" ||
formData.repeatPassword.value === "" ||
formData.repeatPassword.value === ""
}
onPress={async () => {
setSubmittingForm(true);
try {
const resp = await onSubmit(
formData.username.value,
formData.password.value,
formData.email.value
);
toast.show({ description: resp });
} catch (e) {
toast.show({ description: e as string });
} finally {
setSubmittingForm(false);
}
}}
>
{translate("signUp")}
</Button>
</FormControl>
</Stack>
</Box>
</Box>
);
};
export default LoginForm;
+61 -4
View File
@@ -11,6 +11,7 @@ export const en = {
chapters: 'Chapters',
bestScore: 'Best Score',
lastScore: 'Last Score',
play: 'Play',
langBtn: 'Language',
diffBtn: 'Difficulty',
backBtn: 'Back',
@@ -27,7 +28,6 @@ export const en = {
dark: 'Dark',
system: 'System',
light: 'Light',
play: 'Play',
goNextStep: 'Step Up!',
mySkillsToImprove: "My Competencies to work on",
recentlyPlayed: 'Recently played',
@@ -43,6 +43,25 @@ export const en = {
arpegeCompetency: 'Arpeges',
chordsCompetency: 'Chords',
usernameTooShort: 'Username is too short',
passwordTooShort: 'Password is too short',
usernameTooLong: 'Username is too long',
passwordTooLong: 'Password is too long',
username: 'Username',
password: 'Password',
login: 'Login',
invalidCredentials: 'Invalid credentials',
forgottenPassword: 'Forgotten password',
invalidEmail: 'Invalid email',
email: 'Email',
repeatPassword: 'Repeat password',
passwordsDontMatch: 'Passwords don\'t match',
signUp: 'Sign up',
signIn: 'Sign in',
"Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character": "Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character",
accountCreated: 'Account created',
loggedIn: 'Logged in',
usernameTaken: 'Username already taken',
};
export const fr: typeof en = {
@@ -58,6 +77,7 @@ export const fr: typeof en = {
chapters: 'Chapitres',
bestScore: 'Meilleur Score',
lastScore: 'Dernier Score',
play: 'Jouer',
langBtn: 'Langage',
diffBtn: 'Difficulté',
backBtn: 'Retour',
@@ -73,7 +93,6 @@ export const fr: typeof en = {
dark: 'Foncé',
system: 'Système',
light: 'Clair',
play: 'Jouer',
settingsBtn: "Réglages",
goNextStep: "Prochaine Etape",
mySkillsToImprove: "Mes Skills",
@@ -86,7 +105,26 @@ export const fr: typeof en = {
leftHandCompetency: "Main gauche",
accuracyCompetency: "Justesse",
arpegeCompetency: "Arpege",
chordsCompetency: "Accords"
chordsCompetency: "Accords",
usernameTooShort: 'Le nom d\'utilisateur est trop court',
passwordTooShort: 'Le mot de passe est trop court',
usernameTooLong: 'Le nom d\'utilisateur est trop long',
passwordTooLong: 'Le mot de passe est trop long',
username: 'Nom d\'utilisateur',
password: 'Mot de passe',
login: 'Se connecter',
invalidCredentials: "Informations d'identification invalides",
loggedIn: 'Connecté',
usernameTaken: 'Nom d\'utilisateur déjà pris',
forgottenPassword: "Mot de passe oublié",
invalidEmail: "Email invalide",
email: "Email",
repeatPassword: "Confirmer",
passwordsDontMatch: "Mots de passes différents",
signUp: "S'inscrire",
signIn: "Se connecter",
"Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character": "",
accountCreated: "Compte créé"
};
export const sp: typeof en = {
@@ -132,5 +170,24 @@ export const sp: typeof en = {
chapters: "Chapitres",
bestScore: "Meilleur Score",
lastScore: "Dernier score",
recentlyPlayed: "Joués récemment"
recentlyPlayed: "Joués récemment",
usernameTooShort: 'Le nom d\'utilisateur est trop court',
passwordTooShort: 'Le mot de passe est trop court',
usernameTooLong: 'Le nom d\'utilisateur est trop long',
passwordTooLong: 'Le mot de passe est trop long',
username: 'Nom d\'utilisateur',
password: 'Mot de passe',
login: 'Se connecter',
invalidCredentials: "Informations d'identification invalides",
forgottenPassword: 'Mot de passe oublié',
invalidEmail: 'Email invalide',
email: 'Email',
repeatPassword: 'Répéter le mot de passe',
passwordsDontMatch: 'Les mots de passe ne correspondent pas',
signUp: 'S\'inscrire',
signIn: 'Se connecter',
"Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character": "Doit contenir 8 caractères, une majuscule, une minuscule, un chiffre et un caractère spécial",
accountCreated: 'Compte créé',
loggedIn: 'Connectado',
usernameTaken: 'Nombre de usuario ya tomado',
};
+2 -1
View File
@@ -40,7 +40,8 @@
"react-native-svg": "12.3.0",
"react-native-testing-library": "^6.0.0",
"react-native-web": "0.17.7",
"react-redux": "^8.0.2"
"react-redux": "^8.0.2",
"yup": "^0.32.11"
},
"devDependencies": {
"@babel/core": "^7.12.9",
+36 -4
View File
@@ -1,20 +1,52 @@
import React from "react";
import { useDispatch } from '../state/Store';
import { translate } from "../i18n/i18n";
import API from "../API";
import { setUserToken } from "../state/UserSlice";
import { Center, Button, Text } from 'native-base';
import SigninForm from "../components/forms/signinform";
import SignupForm from "../components/forms/signupform";
const hanldeSignin = async (username: string, password: string, tokenSetter: (token: string) => void): Promise<string> => {
try {
const response = await API.checkSigninCredentials(username, password);
tokenSetter(response);
return translate("loggedIn");
} catch (error) {
return error as string;
}
};
const handleSignup = async (username: string, password: string, email: string, tokenSetter: (t: string) => void): Promise<string> => {
try {
const response = await API.checkSignupCredentials(username, password, email);
tokenSetter(response);
return translate("loggedIn");
} catch (error) {
return error as string;
}
};
const AuthenticationView = () => {
const dispatch = useDispatch();
const [mode, setMode] = React.useState("signin" as "signin" | "signup");
return (
<Center style={{ flex: 1 }}>
<Text>{translate('welcome')}</Text>
<Button variant='ghost' onPress={() => dispatch(setUserToken('kkkk'))}>
{translate('signinBtn')}
{mode === "signin" ? (<>
<Text fontWeight='thin'>username, password:</Text>
<Text fontWeight='thin'>katerina, 1234</Text>
<SigninForm onSubmit={(username, password) => hanldeSignin(username, password, (token) => dispatch(setUserToken(token)))} />
</>) : (
<SignupForm onSubmit={(username, password, email) => handleSignup(username, password, email, (token) => dispatch(setUserToken(token)))} />
)}
{ mode ==="signin" && <Button variant="outline" marginTop={5} colorScheme="error" >{translate("forgottenPassword")}</Button> }
<Button variant='outline' marginTop={5} colorScheme='primary' onPress={() => setMode(mode === "signin" ? "signup" : "signin")}>
<Text>{translate(mode === "signin" ? "signUp" : "signIn")}</Text>
</Button>
</Center>
);
}
};
export default AuthenticationView;
+391 -355
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1