Merge commit '958e62d733e19813e76a2f9d5d49d46ac1eebbc2' into feature/initial_frontend

This commit is contained in:
keannu125 2023-04-08 16:47:25 +08:00
commit 7388b851d5
11 changed files with 5071 additions and 641 deletions

View file

@ -10,6 +10,9 @@ import UserInfo from "./src/Routes/UserInfo/UserInfo";
import AddNote from "./src/Routes/AddNote/AddNote"; import AddNote from "./src/Routes/AddNote/AddNote";
import Login from "./src/Routes/Login/Login"; import Login from "./src/Routes/Login/Login";
import Register from "./src/Routes/Register/Register"; import Register from "./src/Routes/Register/Register";
import { Provider } from "react-redux";
import Store from "./src/Features/Redux/Store/Store";
import { QueryClient, QueryClientProvider } from "react-query";
const Drawer = createDrawerNavigator(); const Drawer = createDrawerNavigator();
@ -17,6 +20,7 @@ const Drawer = createDrawerNavigator();
export default function App() { export default function App() {
return ( return (
<Provider store={Store}>
<NavigationContainer> <NavigationContainer>
<Drawer.Navigator <Drawer.Navigator
initialRouteName="Home" initialRouteName="Home"
@ -31,5 +35,6 @@ export default function App() {
</Drawer.Navigator> </Drawer.Navigator>
</NavigationContainer> </NavigationContainer>
</Provider>
); );
} }

5070
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -9,15 +9,20 @@
"web": "expo start --web" "web": "expo start --web"
}, },
"dependencies": { "dependencies": {
"@expo/webpack-config": "^18.0.1",
"@react-native-async-storage/async-storage": "^1.18.1",
"@react-navigation/drawer": "^6.6.2", "@react-navigation/drawer": "^6.6.2",
"@react-navigation/native": "^6.1.6", "@react-navigation/native": "^6.1.6",
"@react-navigation/native-stack": "^6.9.12", "@react-navigation/native-stack": "^6.9.12",
"@react-navigation/stack": "^6.3.16", "@react-navigation/stack": "^6.3.16",
"@reduxjs/toolkit": "^1.9.3",
"axios": "^1.3.4",
"expo": "~48.0.5", "expo": "~48.0.5",
"expo-linear-gradient": "^12.1.1", "expo-linear-gradient": "^12.1.1",
"expo-status-bar": "~1.4.4", "expo-status-bar": "~1.4.4",
"react": "18.2.0", "react": "18.2.0",
"react-native": "0.71.3", "react-dom": "18.2.0",
"react-native": "0.71.6",
"react-native-gesture-handler": "~2.9.0", "react-native-gesture-handler": "~2.9.0",
"react-native-paper": "^5.4.0", "react-native-paper": "^5.4.0",
"react-native-pell-rich-editor": "^1.8.8", "react-native-pell-rich-editor": "^1.8.8",
@ -25,11 +30,16 @@
"react-native-safe-area-context": "4.5.0", "react-native-safe-area-context": "4.5.0",
"react-native-screens": "~3.20.0", "react-native-screens": "~3.20.0",
"react-native-svg": "13.4.0", "react-native-svg": "13.4.0",
"react-native-webview": "^11.26.1" "react-native-webview": "^11.26.1",
"react-native-web": "~0.18.10",
"react-query": "^3.39.3",
"react-redux": "^8.0.5",
"redux": "^4.2.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.0", "@babel/core": "^7.20.0",
"@types/react": "~18.0.14", "@types/react": "~18.0.14",
"@types/react-native": "^0.71.5",
"typescript": "^4.9.4" "typescript": "^4.9.4"
}, },
"private": true "private": true

142
src/Components/Api/Api.tsx Normal file
View file

@ -0,0 +1,142 @@
import axios from "axios";
import AsyncStorage from "@react-native-async-storage/async-storage";
import {
ActivationParams,
UpdateNoteParams,
AddNoteParams,
LoginParams,
RegistrationParams,
} from "../../Interfaces/Interfaces";
// Note APIs
const instance = axios.create({
baseURL: "https://keannu126.pythonanywhere.com",
});
export async function GetNotes() {
const token = JSON.parse(await AsyncStorage.getItem("token") || "{}");
return instance
.get("/api/v1/notes/", {
headers: {
Authorization: "Token " + token,
},
})
.then((response) => {
return response.data;
});
}
export function GetPublicNotes() {
return instance.get("/api/v1/public_notes/").then((response) => {
return response.data;
});
}
export async function GetNote(id: number) {
const token = JSON.parse(await AsyncStorage.getItem("token") || "{}");
return instance
.get("/api/v1/notes/" + id + "/", {
headers: {
Authorization: "Token " + token,
},
})
.then((response) => {
return response.data;
});
}
export async function UpdateNote(note: UpdateNoteParams) {
const token = JSON.parse(await AsyncStorage.getItem("token") || "{}");
return instance
.patch("/api/v1/notes/" + note.id + "/", note, {
headers: {
Authorization: "Token " + token,
},
})
.then((response) => {
return response.data;
})
.catch((error) => {
return error;
});
}
export async function AddNote(note: AddNoteParams) {
const token = JSON.parse(await AsyncStorage.getItem("token") || "{}");
return instance
.post("/api/v1/notes/", note, {
headers: {
Authorization: "Token " + token,
},
})
.then((response) => {
return response.data;
})
.catch((error) => {
return error;
});
}
export async function DeleteNote(id: number) {
const token = JSON.parse(await AsyncStorage.getItem("token") || "{}");
return instance
.delete("/api/v1/notes/" + id + "/", {
headers: {
Authorization: "Token " + token,
},
})
.catch((error) => {
return error;
});
}
// User APIs
export function UserRegister(register: RegistrationParams) {
return instance
.post("/api/v1/accounts/users/", register)
.then(async (response) => {
return true;
})
.catch((error) => {
return false;
});
}
export function UserLogin(user: LoginParams) {
return instance
.post("/api/v1/accounts/token/login/", user)
.then(async (response) => {
AsyncStorage.setItem("token", JSON.stringify(response.data.auth_token));
return true;
})
.catch((error) => {
console.log("Login Failed: " + error);
return false;
});
}
export async function UserInfo() {
const token = JSON.parse(await AsyncStorage.getItem("token") || "{}");
return instance
.get("/api/v1/accounts/users/me/", {
headers: {
Authorization: "Token " + token,
},
})
.then((response) => {
return response.data;
});
}
export function UserActivate(activation: ActivationParams) {
return instance
.post("/api/v1/accounts/users/activation/", activation)
.then(async (response) => {
return true;
})
.catch((error) => {
return false;
});
}

View file

@ -0,0 +1,33 @@
import { createSlice } from "@reduxjs/toolkit";
export const LoggedInUserSlice = createSlice({
name: "Login",
initialState: {
value: {
email: "",
id: 0,
username: "",
},
},
reducers: {
SetUser: (state, action) => {
state.value = {
email: action.payload.email,
id: action.payload.id,
username: action.payload.username,
};
},
UnsetUser: (state) => {
state.value = {
email: "",
id: 0,
username: "",
};
},
},
});
// Action creators are generated for each case reducer function
export const { SetUser, UnsetUser } = LoggedInUserSlice.actions;
export default LoggedInUserSlice.reducer;

View file

@ -0,0 +1,21 @@
import { createSlice } from "@reduxjs/toolkit";
export const LoginSlice = createSlice({
name: "Login",
initialState: {
logged_in: false,
},
reducers: {
SetLoggedIn: (state) => {
state.logged_in = !state.logged_in;
},
SetLoggedOut: (state) => {
state.logged_in = !state.logged_in;
},
},
});
// Action creators are generated for each case reducer function
export const { SetLoggedIn, SetLoggedOut } = LoginSlice.actions;
export default LoginSlice.reducer;

View file

@ -0,0 +1,10 @@
import { configureStore } from "@reduxjs/toolkit";
import LoginReducer from "../Slices/LoginSlice/LoginSlice";
import LoggedInUserReucer from "../Slices/LoggedInUserSlice/LoggedInUserSlice";
export default configureStore({
reducer: {
Login: LoginReducer,
LoggedInUser: LoggedInUserReucer,
},
});

View file

@ -6,3 +6,62 @@ export interface IconProps {
export interface RootDrawerParamList { export interface RootDrawerParamList {
navigate: any; navigate: any;
} }
// Redux Interfaces
export interface LoginState {
Login: { logged_in: boolean };
}
export interface LoggedInUserState {
LoggedInUser: {
value: {
email: string;
id: number;
username: string;
};
};
}
// Component Props Interfaces
export interface NoteProps {
title: string;
content: string;
id: number;
date_created: Date;
owner: string;
}
export interface IconProps {
size: number;
color: string;
}
// API Interfaces
export interface RegistrationParams {
email: string;
username: string;
password: string;
}
export interface LoginParams {
username: string;
password: string;
}
export interface ActivationParams {
uid: string;
token: string;
}
export interface AddNoteParams {
title: string;
content: string;
}
export interface UpdateNoteParams {
id: number;
title: string;
content: string;
}

View file

@ -1,34 +1,40 @@
import * as React from 'react'; import * as React from "react";
import {View, Text} from 'react-native'; import { View, Text, TouchableOpacity } from "react-native";
import styles from '../../styles'; import styles from "../../styles";
import Background from '../../Components/Background/Background'; import Background from "../../Components/Background/Background";
import AppIcon from "../../Components/Icons/AppIcon/AppIcon";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import {TouchableOpacity,} from "react-native"; import { useDispatch } from "react-redux";
import { RootDrawerParamList } from "../../Interfaces/Interfaces"; import { RootDrawerParamList } from "../../Interfaces/Interfaces";
export default function Home() { export default function Home() {
const navigation = useNavigation<RootDrawerParamList>(); const navigation = useNavigation<RootDrawerParamList>();
const dispatch = useDispatch();
return ( return (
<Background> <Background>
<Text style={{...styles.text_white, ...{fontSize: 25}}}>Clip Notes</Text> <Text style={{ ...styles.text_white, ...{ fontSize: 25 } }}>
Clip Notes
</Text>
<View <View
style={{ style={{
display: 'flex', display: "flex",
flexDirection: 'row', flexDirection: "row",
alignItems: 'center', alignItems: "center",
justifyContent: 'center', justifyContent: "center",
}}> }}
<View style={styles.homecont}> >
<TouchableOpacity <View style={styles.homecont}>
onPress={() => { <TouchableOpacity
navigation.navigate("Add Note"); onPress={() => {
}}> navigation.navigate("Add Note");
<Text style={styles.newnote}>+</Text> }}
</TouchableOpacity> >
<Text style={styles.no}>New note...</Text> <Text style={styles.newnote}>+</Text>
<View style={{margin: 16}} /> </TouchableOpacity>
</View> <Text style={styles.no}>New note...</Text>
<View style={{ margin: 16 }} />
</View>
</View> </View>
</Background> </Background>
); );

View file

@ -1,61 +1,99 @@
import * as React from 'react'; import * as React from "react";
import {View, Text, TextInput} from 'react-native'; import { View, Text, TextInput } from "react-native";
import styles from '../../styles'; import styles from "../../styles";
import Background from '../../Components/Background/Background'; import Background from "../../Components/Background/Background";
import { SafeAreaView} from "react-native"; import {
SafeAreaView,
NativeSyntheticEvent,
TextInputChangeEventData,
} from "react-native";
import { StatusBar } from "expo-status-bar"; import { StatusBar } from "expo-status-bar";
import { useState } from "react"; import { useState } from "react";
import {TouchableOpacity,} from "react-native"; import { TouchableOpacity } from "react-native";
import {
NavigationHelpersContext,
useNavigation,
} from "@react-navigation/native";
import { useDispatch } from "react-redux";
import { SetUser } from "../../Features/Redux/Slices/LoggedInUserSlice/LoggedInUserSlice";
import { SetLoggedIn } from "../../Features/Redux/Slices/LoginSlice/LoginSlice";
import { UserInfo, UserLogin } from "../../Components/Api/Api";
import { RootDrawerParamList } from "../../Interfaces/Interfaces"; import { RootDrawerParamList } from "../../Interfaces/Interfaces";
import { useNavigation } from "@react-navigation/native";
export default function Login() { export default function Login() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const navigation = useNavigation<RootDrawerParamList>(); const navigation = useNavigation<RootDrawerParamList>();
const dispatch = useDispatch();
const [user, setUser] = useState({
username: "",
password: "",
});
const [error, setError] = useState("");
return ( return (
<Background> <Background>
<Text style={{...styles.text_white, ...{fontSize: 32}}}></Text> <Text style={{ ...styles.text_white, ...{ fontSize: 32 } }}></Text>
<SafeAreaView> <SafeAreaView>
<View style={styles.container}> <View style={styles.container}>
<Text style ={styles.loginlabel}>Login to Clip Notes</Text> <Text style={styles.loginlabel}>Login to Clip Notes</Text>
<StatusBar style="auto" /> <StatusBar style="auto" />
<View style={styles.inputView}> <View style={styles.inputView}>
<TextInput <TextInput
style={styles.TextInput} style={styles.TextInput}
placeholder="Username" placeholder="Username"
placeholderTextColor="white" placeholderTextColor="white"
onChangeText={setUsername} value={user.username}
value={username} maxLength={20}
/> onChange={(
</View> e: NativeSyntheticEvent<TextInputChangeEventData>
<View style={styles.inputView}> ): void => {
<TextInput setUser({ ...user, username: e.nativeEvent.text });
style={styles.TextInput} console.log(user.username);
placeholder="Password" }}
placeholderTextColor="white" />
secureTextEntry={true} </View>
onChangeText={setPassword}
value={password}
/>
</View>
<TouchableOpacity> <View style={styles.inputView}>
<Text style={styles.forgot_button}>Forgot Password?</Text> <TextInput
</TouchableOpacity> style={styles.TextInput}
<TouchableOpacity style={styles.loginBtn}> placeholder="Password"
<Text style={styles.loginText}>LOGIN</Text> placeholderTextColor="white"
</TouchableOpacity> secureTextEntry={true}
<TouchableOpacity style={styles.registerbtn} value={user.password}
onPress={() => { onChange={(
navigation.navigate("Register"); e: NativeSyntheticEvent<TextInputChangeEventData>
}}> ): void => {
<Text style={styles.registertext}>REGISTER</Text> setUser({ ...user, password: e.nativeEvent.text });
</TouchableOpacity> console.log(user.password);
</View> }}
/>
</View>
<TouchableOpacity>
<Text style={styles.forgot_button}>Forgot Password?</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={async () => {
setUser({
username: "",
password: "",
});
if (await UserLogin(user)) {
await dispatch(SetLoggedIn());
await dispatch(SetUser(await UserInfo()));
navigation.navigate("Home");
} else {
setError("Invalid Login");
}
}}
style={styles.loginBtn}
>
<Text style={styles.loginText}>LOGIN</Text>
</TouchableOpacity>
<Text style={styles.text_small_red}>{error}</Text>
<TouchableOpacity style={styles.registerbtn}>
<Text style={styles.registertext}>REGISTER</Text>
</TouchableOpacity>
</View>
</SafeAreaView> </SafeAreaView>
</Background> </Background>
); );
}; }

View file

@ -1,45 +1,43 @@
import {StyleSheet} from 'react-native'; import { StyleSheet } from "react-native";
const styles = StyleSheet.create({ const styles = StyleSheet.create({
background: { background: {
height: '100%', height: "100%",
width: '100%', width: "100%",
backgroundColor:'#002d4d', backgroundColor: "#002d4d",
position: 'absolute', position: "absolute",
}, },
text_white: { text_white: {
color: 'white', color: "white",
fontWeight: 'bold', fontWeight: "bold",
textAlign: 'center', textAlign: "center",
}, },
button_generic: { button_generic: {
justifyContent: 'center', justifyContent: "center",
alignSelf: 'center', alignSelf: "center",
alignItems: 'center', alignItems: "center",
display: 'flex', display: "flex",
flexDirection: 'row', flexDirection: "row",
margin: 8, margin: 8,
padding: 8, padding: 8,
borderRadius: 16, borderRadius: 16,
}, },
flex_row: { flex_row: {
display: 'flex', display: "flex",
flexDirection: 'row', flexDirection: "row",
alignItems: 'center', alignItems: "center",
}, },
flex_column: { flex_column: {
display: 'flex', display: "flex",
flexDirection: 'column', flexDirection: "column",
alignItems: 'center', alignItems: "center",
}, },
input: { input: {
height: 40, height: 40,
margin: 12, margin: 12,
borderWidth: 1, borderWidth: 1,
}, },
inputView: { inputView: {
backgroundColor: "black", backgroundColor: "black",
borderRadius: 30, borderRadius: 30,
@ -48,15 +46,15 @@ const styles = StyleSheet.create({
marginBottom: 20, marginBottom: 20,
}, },
TextInput: { TextInput: {
color: 'white', color: "white",
height: 50, height: 50,
flex: 1, flex: 1,
padding: 10, padding: 10,
marginLeft: 5, marginLeft: 5,
}, },
forgot_button: { forgot_button: {
fontWeight: 'bold', fontWeight: "bold",
color: 'white', color: "white",
height: 30, height: 30,
marginBottom: 10, marginBottom: 10,
}, },
@ -77,54 +75,53 @@ const styles = StyleSheet.create({
marginTop: 40, marginTop: 40,
backgroundColor: "orange", backgroundColor: "orange",
}, },
loginText:{ loginText: {
color: 'white', color: "white",
fontWeight: 'bold', fontWeight: "bold",
fontSize: 20, fontSize: 20,
}, },
registertext:{ registertext: {
color: 'white', color: "white",
fontWeight: 'bold', fontWeight: "bold",
fontSize: 20, fontSize: 20,
}, },
container: { container: {
marginTop: 30, marginTop: 30,
marginLeft: 22, marginLeft: 22,
height: 500, height: 500,
width: 350, width: 350,
borderRadius: 25, borderRadius: 25,
backgroundColor: 'blue', backgroundColor: "blue",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
}, },
loginlabel:{ loginlabel: {
fontWeight: 'bold', fontWeight: "bold",
fontSize: 25, fontSize: 25,
marginBottom: 30, marginBottom: 30,
color: 'white', color: "white",
}, },
box:{ box: {
width: '100%', width: "100%",
height: 200, height: 200,
}, },
userinfocont:{ userinfocont: {
marginTop: 10, marginTop: 10,
marginLeft: 50, marginLeft: 50,
height: 80, height: 80,
width: 300, width: 300,
borderRadius: 25, borderRadius: 25,
backgroundColor: 'orange', backgroundColor: "orange",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
}, },
userlabel:{ userlabel: {
fontWeight: 'bold', fontWeight: "bold",
fontSize: 25, fontSize: 25,
marginBottom: 10, marginBottom: 10,
color: 'white', color: "white",
}, },
addnote:{ addnote: {
marginLeft: 180, marginLeft: 180,
width: "10%", width: "10%",
borderRadius: 25, borderRadius: 25,
@ -133,61 +130,61 @@ const styles = StyleSheet.create({
justifyContent: "center", justifyContent: "center",
backgroundColor: "lightgreen", backgroundColor: "lightgreen",
}, },
addtext:{ addtext: {
fontSize: 20, fontSize: 20,
color: 'white', color: "white",
}, },
addnotescont:{ addnotescont: {
marginTop: 20, marginTop: 20,
marginLeft: 20, marginLeft: 20,
height: 200, height: 200,
width: 350, width: 350,
backgroundColor: 'blue', backgroundColor: "blue",
alignItems: 'flex-start', alignItems: "flex-start",
justifyContent: 'center', justifyContent: "center",
}, },
no: { no: {
fontSize: 20, fontSize: 20,
color: 'white', color: "white",
marginTop: 20, marginTop: 20,
alignItems: 'center' alignItems: "center",
}, },
typehere:{ typehere: {
backgroundColor: "black", backgroundColor: "black",
borderRadius: 10, borderRadius: 10,
width: "75%", width: "75%",
height: 300, height: 300,
marginTop: 20, marginTop: 20,
}, },
typeinput: { typeinput: {
color: 'white', color: "white",
flex: 1, flex: 1,
paddingBottom: 250, paddingBottom: 250,
marginLeft: 5, marginLeft: 5,
}, },
title:{ title: {
color: 'white', color: "white",
flex: 1, flex: 1,
marginLeft: 5, marginLeft: 5,
}, },
tle:{ tle: {
backgroundColor: "black", backgroundColor: "black",
borderRadius: 10, borderRadius: 10,
width: "80%", width: "80%",
height: 40, height: 40,
marginTop: 20, marginTop: 20,
}, },
addnotecont:{ addnotecont: {
marginTop: 30, marginTop: 30,
marginLeft: 22, marginLeft: 22,
height: 500, height: 500,
width: 350, width: 350,
borderRadius: 25, borderRadius: 25,
backgroundColor: '#7cb9e8', backgroundColor: "#7cb9e8",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
}, },
savebtn:{ savebtn: {
width: "40%", width: "40%",
borderRadius: 25, borderRadius: 25,
height: 40, height: 40,
@ -196,7 +193,7 @@ const styles = StyleSheet.create({
marginTop: 10, marginTop: 10,
backgroundColor: "green", backgroundColor: "green",
}, },
cancelbtn:{ cancelbtn: {
width: "40%", width: "40%",
borderRadius: 25, borderRadius: 25,
height: 40, height: 40,
@ -205,28 +202,28 @@ const styles = StyleSheet.create({
marginTop: 10, marginTop: 10,
backgroundColor: "green", backgroundColor: "green",
}, },
savenote:{ savenote: {
color: 'white', color: "white",
fontWeight: 'bold', fontWeight: "bold",
fontSize: 20, fontSize: 20,
}, },
cancel:{ cancel: {
color: 'white', color: "white",
fontWeight: 'bold', fontWeight: "bold",
fontSize: 20, fontSize: 20,
}, },
homecont:{ homecont: {
marginTop: 30, marginTop: 30,
height: 150, height: 150,
width: 350, width: 350,
borderRadius: 25, borderRadius: 25,
backgroundColor: '#7cb9e8', backgroundColor: "#7cb9e8",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
}, },
newnote:{ newnote: {
color: 'white', color: "white",
fontWeight: 'bold', fontWeight: "bold",
fontSize: 50, fontSize: 50,
}, },
// //
@ -238,7 +235,7 @@ const styles = StyleSheet.create({
width: "60%", width: "60%",
height: 50, height: 50,
marginTop: 50, marginTop: 50,
marginLeft:10, marginLeft: 10,
justifyContent: "center", justifyContent: "center",
}, },
label: { label: {
@ -250,7 +247,6 @@ const styles = StyleSheet.create({
disabledInput: { disabledInput: {
backgroundColor: "white", backgroundColor: "white",
borderRadius: 10, borderRadius: 10,
}, },
editButton: { editButton: {
backgroundColor: "green", backgroundColor: "green",
@ -260,19 +256,25 @@ const styles = StyleSheet.create({
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
marginTop: 20, marginTop: 20,
marginLeft: 20 marginLeft: 20,
}, },
buttonText:{ buttonText: {
color: "white", color: "white",
}, },
inputUser:{ inputUser: {
marginLeft: 10, marginLeft: 10,
height: 40, height: 40,
width: "100%", width: "100%",
color: "black", color: "black",
backgroundColor: "white", backgroundColor: "white",
borderRadius: 10, borderRadius: 10,
} },
text_small_red: {
color: "#bc231e",
fontSize: 10,
fontWeight: "bold",
textAlign: "center",
},
}); });
export default styles; export default styles;