Commit eb81d4bf by G

feat: implemented a functional login screen leveraging redux store to…

feat: implemented a functional login screen leveraging redux store to automatically redirect to the home screen once successfully logged in.
parent 168ea949
import { axiosInstance } from "@/axios"; import { axiosInstance } from "@/axios";
import type { LoginPayload, Token } from "./types"; import type { LoginPayload, Token, UserData } from "./types";
export const login = (data: LoginPayload) => { export const login = (data: LoginPayload) => {
return axiosInstance.post<Token>("/login/token/", data); return axiosInstance.post<Token>("/login/token/", data);
...@@ -7,5 +7,5 @@ export const login = (data: LoginPayload) => { ...@@ -7,5 +7,5 @@ export const login = (data: LoginPayload) => {
// On this one you just provide the bear token and its should return the userData. // On this one you just provide the bear token and its should return the userData.
export const getUserData = () => { export const getUserData = () => {
return axiosInstance.get<Token>("/login/token/"); return axiosInstance.get<UserData>("/user-info/");
}; };
import { asp as g } from "@asp/asp";
import * as Button from "@components/ButtonNew";
import * as Input from "@components/InputNew";
import * as Modal from "@components/Modal";
import { MaterialIcons } from "@expo/vector-icons";
import Feather from "@expo/vector-icons/Feather";
import { useMutation } from "@tanstack/react-query";
import type { AxiosError } from "axios";
import { useRef, useState } from "react";
import { Text, View } from "react-native";
import { useDispatch } from "react-redux";
import { getUserData, login } from "../api";
import { setToken, setUserData } from "../slice";
export const LoginForm = () => {
const dispatch = useDispatch();
const usernameRef = useRef<string>("");
const passwordRef = useRef<string>("");
const loginMutation = useMutation({
mutationFn: () => login({ username: usernameRef.current, password: passwordRef.current }),
onSuccess: (data) => {
dispatch(setToken(data.data));
useDataMutation.mutate();
},
onError: (error: AxiosError) => {
setError(JSON.stringify(error.response?.data) || error.message);
},
});
const useDataMutation = useMutation({
mutationFn: getUserData,
onSuccess: (data) => {
dispatch(setUserData(data.data));
},
});
const [error, setError] = useState<string>("");
return (
<View
style={[
g.p_lg,
g.gap_lg,
{
backgroundColor: "white",
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
},
]}
>
<Text style={[g.text_5xl, g.font_bold]}>Connexion</Text>
<Text style={{ color: "gray" }}>Bienvenue, vous nous avez manqué !</Text>
<Input.Container>
<Input.Header>Addresse e-mail</Input.Header>
<Input.FieldContainer>
<Feather name="smartphone" size={24} color="black" />
<Input.Field onChangeText={(v) => (usernameRef.current = v)} />
</Input.FieldContainer>
</Input.Container>
<Input.Container>
<Input.Header>Mot de passe</Input.Header>
<Input.FieldContainer>
<MaterialIcons name="lock-outline" size={24} color="black" />
<Input.Field secureTextEntry onChangeText={(v) => (passwordRef.current = v)} />
</Input.FieldContainer>
</Input.Container>
<Button.Container
style={[g.mt_5xl]}
isLoading={loginMutation.isPending}
onPress={() => loginMutation.mutate()}
>
<Button.Label style={{ color: "white" }}>Se connecter</Button.Label>
</Button.Container>
<Button.Container style={[{ backgroundColor: "#e8e8e9aa" }]}>
<Button.Label style={{ color: "black" }}>Créer un compte</Button.Label>
</Button.Container>
{/* MODAL */}
<Modal.SafeView>
<Modal.RnModal visible={!!error} style={{ backgroundColor: "rgba(0, 0, 0, 0.25)" }}>
<Modal.OuterView
style={[g.p_md, g.shadow_elevated, { backgroundColor: "white" }]}
>
<Text>{error}</Text>
<Button.Container onPress={() => setError("")}>
<Button.Label>OK</Button.Label>
</Button.Container>
</Modal.OuterView>
</Modal.RnModal>
</Modal.SafeView>
</View>
);
};
...@@ -18,6 +18,13 @@ export const authSlice = createSlice({ ...@@ -18,6 +18,13 @@ export const authSlice = createSlice({
name: "auth", name: "auth",
initialState: initialState, initialState: initialState,
reducers: { reducers: {
setToken: (state, action: PayloadAction<Token>) => {
state.token = action.payload;
},
setUserData: (state, action: PayloadAction<UserData>) => {
state.user = action.payload;
state.isAuthenticated = true;
},
login: (state, action: PayloadAction<UserDataAndToken>) => { login: (state, action: PayloadAction<UserDataAndToken>) => {
state.isAuthenticated = true; state.isAuthenticated = true;
state.user = action.payload.user; state.user = action.payload.user;
...@@ -29,5 +36,5 @@ export const authSlice = createSlice({ ...@@ -29,5 +36,5 @@ export const authSlice = createSlice({
}, },
}); });
export const { login, logout } = authSlice.actions; export const { login, logout, setToken, setUserData } = authSlice.actions;
export const authReducer = authSlice.reducer; export const authReducer = authSlice.reducer;
import { useUserAuthenticationContext } from "@/contexts/UserAuthenticationContext";
import { LOG } from "@logger"; import { LOG } from "@logger";
import { createNativeStackNavigator } from "@react-navigation/native-stack"; import { createNativeStackNavigator } from "@react-navigation/native-stack";
import HomeUserNotLoggedIn from "@screens/HomeUserNotLoggedIn"; import HomeUserNotLoggedIn from "@screens/HomeUserNotLoggedIn";
...@@ -6,6 +5,8 @@ import PaymentResultScreen from "@screens/PaymentResultScreen"; ...@@ -6,6 +5,8 @@ import PaymentResultScreen from "@screens/PaymentResultScreen";
import UserLoginScreen from "@screens/UserLoginScreen"; import UserLoginScreen from "@screens/UserLoginScreen";
import WaveQrCodePaymentScreen from "@screens/WaveQrCodePaymentScreen"; import WaveQrCodePaymentScreen from "@screens/WaveQrCodePaymentScreen";
import { memo } from "react"; import { memo } from "react";
import { useSelector } from "react-redux";
import type { RootState } from "@/redux";
import { AppBottomTabsNavigator } from "./AppBottomTabsNavigator"; import { AppBottomTabsNavigator } from "./AppBottomTabsNavigator";
import type { ImainStackNavigator } from "./Types"; import type { ImainStackNavigator } from "./Types";
...@@ -50,6 +51,6 @@ const AppMainStackNavigator: React.FC<IappMainStackNavigatorProps> = ({ isAuthen ...@@ -50,6 +51,6 @@ const AppMainStackNavigator: React.FC<IappMainStackNavigatorProps> = ({ isAuthen
export default memo(AppMainStackNavigator); export default memo(AppMainStackNavigator);
export const AppMainStackNavigatorAuthWrapper = () => { export const AppMainStackNavigatorAuthWrapper = () => {
const { isAuthenticated } = useUserAuthenticationContext(); const isAuthenticated = useSelector((state: RootState) => state.auth.isAuthenticated);
return <AppMainStackNavigator isAuthenticated={isAuthenticated} />; return <AppMainStackNavigator isAuthenticated={isAuthenticated} />;
}; };
import { useUserAuthenticationContext } from "@/contexts/UserAuthenticationContext"; import { asp as g } from "@asp/asp";
import type { MainStackScreenComponentProps } from "@/navigations/Types";
import Button from "@components/Button";
import InputWithTopLabel from "@components/InputWithTopLabel";
import Box from "@components/bases/Box";
import WrapperWithDefaultBeasyBackgroundAndSafeAreaFull from "@components/wrappers/WrapperWithDefaultBeasyBackgroundAndSafeAreaFull";
import { Fontisto } from "@expo/vector-icons";
import { LOG } from "@logger"; import { LOG } from "@logger";
import Card from "@re-card"; import { ImageBackground, View } from "react-native";
import Text from "@re-text"; import { LoginForm } from "@/features/auth/components/LoginForm";
import { useCallback, useState } from "react"; import type { MainStackScreenComponentProps } from "@/navigations/Types";
import { TouchableOpacity } from "react-native";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import { useSafeAreaInsets } from "react-native-safe-area-context";
const log = LOG.extend("UserLoginScreen"); const log = LOG.extend("UserLoginScreen");
const UserLoginScreen: MainStackScreenComponentProps<"userLoginScreen"> = ({ navigation }) => { const UserLoginScreen: MainStackScreenComponentProps<"userLoginScreen"> = ({ navigation }) => {
log.debug("UserLoginScreen"); log.debug("UserLoginScreen");
const { login, isAuthenticating } = useUserAuthenticationContext();
// TODO : Remove default value for email and password
const [email, setEmail] = useState("admin");
const [password, setPassword] = useState("admin");
const insets = useSafeAreaInsets();
const submit = useCallback(() => {
login(email, password);
}, [email, password, login]);
return ( return (
<WrapperWithDefaultBeasyBackgroundAndSafeAreaFull> <View style={[g.flex_1]}>
<Box height={"100%"}> <ImageBackground
<Box style={{ height: "20%" }} px={"l"}> source={require("@assets/background.png")}
<Box style={[g.flex_1, g.flex_col, g.justify_end]}
px={"m"}
justifyContent={"space-between"}
flexDirection={"row"}
alignItems={"center"}
> >
<TouchableOpacity onPress={() => navigation.goBack()}> <LoginForm />
<Fontisto name="close-a" size={12} color="black" /> </ImageBackground>
</TouchableOpacity> </View>
<TouchableOpacity>
<Text>Mot de passe oublie ?</Text>
</TouchableOpacity>
</Box>
</Box>
<Card variant={"curvedTopContainer"} style={{ marginTop: "auto" }}>
<KeyboardAwareScrollView
// extraScrollHeight={-125}
extraHeight={10}
keyboardOpeningTime={Number.MAX_SAFE_INTEGER}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps={"handled"}
>
<Box p={"l"} paddingTop={"x100"} mb={"s"}>
<Box mb={"l"}>
<Text fontSize={40} fontWeight={"bold"} variant={"black"}>
Connexion
</Text>
<Text color={"gray"}>Bienvenue, vous nous avez manqué !</Text>
</Box>
<Box gap={"m"}>
<InputWithTopLabel
label="Email"
// value={email}
autoCorrect={false}
textContentType="emailAddress"
onChangeText={(email) => setEmail(email)}
/>
<InputWithTopLabel
label="Mot de passe"
secureTextEntry={true}
textContentType="oneTimeCode"
// value={password}
onChangeText={(text) => setPassword(text)}
/>
</Box>
</Box>
<Box p={"s"}>
<Button
variant={"full"}
textVariants={"primary"}
label="Se connecter"
onPress={() => {
submit();
}}
isLoading={isAuthenticating}
/>
<Button
variant={"lightGray"}
textVariants={"black"}
label="Creer un compte"
onPress={() => {}}
/>
</Box>
</KeyboardAwareScrollView>
</Card>
</Box>
<Box
position={"absolute"}
bottom={0}
height={insets.bottom}
backgroundColor={"white"}
width={"100%"}
/>
</WrapperWithDefaultBeasyBackgroundAndSafeAreaFull>
); );
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment