Skip to content

Commit

Permalink
Merge pull request #9 from arkajyotiadhikary/dev-1
Browse files Browse the repository at this point in the history
Add private routes for React Native client
  • Loading branch information
arkajyotiadhikary authored Apr 1, 2024
2 parents 5684a18 + 08139ca commit b4d03e1
Show file tree
Hide file tree
Showing 12 changed files with 193 additions and 71 deletions.
8 changes: 4 additions & 4 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"dependencies": {
"@expo/metro-runtime": "~3.1.3",
"@expo/vector-icons": "^14.0.0",
"@react-native-async-storage/async-storage": "^1.23.0",
"@react-native-async-storage/async-storage": "^1.23.1",
"@react-native-community/slider": "^4.5.0",
"@react-navigation/native": "^6.1.14",
"@react-navigation/stack": "^6.3.25",
Expand Down
31 changes: 2 additions & 29 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,13 @@
import React from "react";

// navigaton import
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
const stack = createStackNavigator<RootStackNavigationProp>();

// state store
import { Provider } from "react-redux";
import { store } from "./store";

// screens
import Home from "./screens/Home.screen";
import Auth from "./screens/Auth.screen";
import PlayerScreen from "./screens/Player.screen";
import Search from "./screens/Search.screen";
import { RootStackNavigationProp } from "../types";
import Router from "./routes/router";

export default function App() {
return (
<Provider store={store}>
<NavigationContainer>
<stack.Navigator
initialRouteName="Home"
screenOptions={{
headerShown: false,
}}
>
<stack.Screen name="Home" component={Home} />
<stack.Screen name="Auth" component={Auth} />
<stack.Screen
name="PlayerScreen"
component={PlayerScreen}
/>
<stack.Screen name="Search" component={Search} />
</stack.Navigator>
</NavigationContainer>
<Router />
</Provider>
);
}
22 changes: 22 additions & 0 deletions client/src/contexts/mAuth.context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React, { createContext, useContext, useState } from "react";

interface AuthContextData {
isAuthenticated: boolean;
setIsAuthenticated: React.Dispatch<React.SetStateAction<boolean>>;
}

export const AuthContext = createContext<AuthContextData | null>(null);

interface AuthProviderProps {
children: React.ReactNode;
}

export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);

const value: AuthContextData = { isAuthenticated, setIsAuthenticated };

return (
<AuthContext.Provider value={value}>{children}</AuthContext.Provider>
);
};
22 changes: 22 additions & 0 deletions client/src/features/user/userSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit";

interface User {
isAuthenticated: boolean;
}

const initUserState: User = {
isAuthenticated: false,
};

export const userSlice = createSlice({
name: "user",
initialState: initUserState,
reducers: {
setCurrentUser(state, action: PayloadAction<User>) {
state.isAuthenticated = action.payload.isAuthenticated;
},
},
});

export const { setCurrentUser } = userSlice.actions;
export const userReducer = userSlice.reducer;
15 changes: 15 additions & 0 deletions client/src/routes/Router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { NavigationContainer } from "@react-navigation/native";
import { AppStack, AuthStack } from "../stacks/mAuth.stack";
import { useSelector } from "react-redux";
import { RootState } from "../store";

const Router = () => {
const userState = useSelector((state: RootState) => state.userReducer);
return (
<NavigationContainer>
{userState.isAuthenticated ? <AppStack /> : <AuthStack />}
</NavigationContainer>
);
};

export default Router;
57 changes: 40 additions & 17 deletions client/src/screens/Auth.screen.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import React, { FC, useState, useEffect } from "react";
import { View, Text, TextInput, TouchableOpacity, Image } from "react-native";
import { useNavigation } from "@react-navigation/native";

import styles from "../styles/Auth.style";
import logo from "../assests/logo/wepik-export-20240324130518XYcX.png";

import { type User } from "../../types";
import { RootStackNavigationProp, type User } from "../../types";

import { signUp, signIn, validateUserInput } from "../services/authService";

import AsyncStorage from "@react-native-async-storage/async-storage";
import { useDispatch } from "react-redux";
import { setCurrentUser } from "../features/user/userSlice";

const Auth: FC = () => {
const navigation = useNavigation<RootStackNavigationProp>();
const dispatch = useDispatch();

// a variable to check is it sign in or sign up form
const [isSignIn, setIsSignIn] = useState(false);
// a object to store user data from the input field
Expand Down Expand Up @@ -40,22 +48,36 @@ const Auth: FC = () => {

// method to handle submit . Check wheather you are submittin sign in form of sign up form
const handleSubmit = async (): Promise<void> => {
console.log("form data:", formData);
const validation = validateUserInput(formData, isSignIn);
if (validation.isValid) {
if (isSignIn) signIn(formData.email, formData.password);
else {
const response = await signUp(
if (!validation.isValid) {
setErrorState(validation.message);
return;
}

try {
let response;
if (isSignIn) {
response = await signIn(formData.email, formData.password);
if (!response?.hasError && "data" in response!) {
await AsyncStorage.setItem("token", response?.data?.token!);
dispatch(setCurrentUser({ isAuthenticated: true }));
}
} else {
response = await signUp(
formData.username,
formData.email,
formData.password
);
if ("message" in response) {
console.log(response);
setErrorState(response?.message);
}
}
} else {
setErrorState(validation.message);

if ("message" in response!) {
setErrorState(response.message);
}
console.log("Response:", response);
} catch (error) {
setErrorState("An error occurred. Please try again."); // Handle generic error message
console.error("Error:", error); // Log the error for debugging
}
};

Expand Down Expand Up @@ -87,17 +109,18 @@ const Auth: FC = () => {
<View style={styles.form}>
<View style={styles.inputHolder}>
<TextInput
placeholder="Username"
value={formData.username}
onChangeText={(e) => handleChange("username", e)}
placeholder="Email"
value={formData.email}
onChangeText={(e) => handleChange("email", e)}
/>
</View>

{!isSignIn && (
<View style={styles.inputHolder}>
<TextInput
placeholder="Email"
value={formData.email}
onChangeText={(e) => handleChange("email", e)}
placeholder="Username"
value={formData.username}
onChangeText={(e) => handleChange("username", e)}
/>
</View>
)}
Expand Down
50 changes: 36 additions & 14 deletions client/src/services/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,21 @@ export const signUp = async (
): Promise<
| { hasError: boolean; User: User }
| { hasError: boolean; message: string }
| unknown
| undefined
> => {
try {
const response: AxiosResponse<
| { hasError: boolean; User: User }
| { hasError: boolean; message: string }
| undefined
> = await axios.post(`${BASE_URL}/api/register`, {
username,
email,
password,
});
return response.data;
return response.data || undefined;
} catch (error) {
return handleAxiosError(error);
return handleAxiosError(error) as undefined;
}
};

Expand All @@ -39,18 +40,28 @@ export const signIn = async (
email: string | undefined,
password: string | undefined
): Promise<
| { hasError: boolean; User: User }
| {
hasError: boolean;
data: {
role: string;
userVerified: boolean;
token: string;
};
}
| { hasError: boolean; message: string }
| undefined
> => {
try {
const response: AxiosResponse<
| { hasError: boolean; User: User }
| {
hasError: boolean;
data: { role: string; userVerified: boolean; token: string };
}
| { hasError: boolean; message: string }
> = await axios.post(`${BASE_URL}/api/login`, { email, password });
return response.data;
return response.data || undefined;
} catch (error) {
handleAxiosError(error);
return handleAxiosError(error) as undefined;
}
};

Expand All @@ -61,8 +72,8 @@ export const validateUserInput = (
): ValidateUserInput => {
// Validate if the username, email, and password are not empty
if (
!formData.username ||
(!formData.email && !isLogIn) ||
(!formData.username && !isLogIn) ||
!formData.email ||
!formData.password
) {
return {
Expand All @@ -72,11 +83,7 @@ export const validateUserInput = (
}

// Validate the email if it's provided and it's not a login operation
if (
!isLogIn &&
formData.email &&
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)
) {
if (formData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
return {
isValid: false,
message: "Invalid email address.",
Expand All @@ -96,3 +103,18 @@ export const validateUserInput = (
message: "",
};
};

export const validateToken = async (token: string): Promise<boolean> => {
try {
const response = await axios.post(`${BASE_URL}/api/validate`, {
token: token,
});
if (!response.data.hasError) {
return true;
}
return false;
} catch (error) {
console.log(error);
return false;
}
};
11 changes: 10 additions & 1 deletion client/src/services/songService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import axios, { AxiosResponse } from "axios";
import { Song } from "../../types";
import { handleAxiosError } from "./axiosErrorHandler";
import { type AddTrack } from "react-native-track-player";
import AsyncStorage from "@react-native-async-storage/async-storage";

const BASE_URL = "http://10.0.2.2:2526";

Expand All @@ -17,6 +18,7 @@ const calculateDurationInSeconds = (durationString: string | undefined) => {
* Formate songs as AddTrack .
* We can only add tracks of type AddTrack in TrackPlayer
*/

const formateSong = (data: Song[]): AddTrack[] => {
return data.map((song, index) => ({
url: song.AudioFilePath,
Expand All @@ -31,9 +33,16 @@ const formateSong = (data: Song[]): AddTrack[] => {

// Get all tracks
export const getAllSong = async (): Promise<AddTrack[] | undefined> => {
const token = await AsyncStorage.getItem("token");
console.log("token", token);
try {
const response: AxiosResponse<Song[]> = await axios.get(
`${BASE_URL}/api/songs`
`${BASE_URL}/api/songs`,
{
headers: {
Authorization: `${token}`,
},
}
);
const formatedSong = formateSong(response.data);
return formatedSong;
Expand Down
30 changes: 30 additions & 0 deletions client/src/stacks/mAuth.stack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createStackNavigator } from "@react-navigation/stack";
import { RootStackParamList } from "../../types";
import Home from "../screens/Home.screen";
import Auth from "../screens/Auth.screen";

const Stack = createStackNavigator<RootStackParamList>();

export const AppStack = () => {
return (
<Stack.Navigator
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="Home" component={Home} />
</Stack.Navigator>
);
};

export const AuthStack = () => {
return (
<Stack.Navigator
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="Auth" component={Auth} />
</Stack.Navigator>
);
};
Loading

0 comments on commit b4d03e1

Please sign in to comment.