Merge branch 'master' into initial-frontend

This commit is contained in:
Keannu Christian Bernasol 2023-07-17 13:55:02 +08:00
commit 22d4aa4a29
21 changed files with 762 additions and 166 deletions

View file

@ -1,17 +1,112 @@
import * as React from "react";
import styles from "../../styles";
import { View, Text } from "react-native";
import { useSelector } from "react-redux";
import { RootState } from "../../features/redux/Store/Store";
import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer";
import { useState, useEffect } from "react";
import MapView, { Marker, UrlTile } from "react-native-maps";
import * as Location from "expo-location";
import GetDistance from "../../components/GetDistance/GetDistance";
import Button from "../../components/Button/Button";
import { colors } from "../../styles";
import { startActivityAsync, ActivityAction } from "expo-intent-launcher";
type LocationType = Location.LocationObject;
export default function Home() {
const creds = useSelector((state: RootState) => state.auth.creds);
const [location, setLocation] = useState<LocationType | null>(null);
const [dist, setDist] = useState<number | null>(null);
const [feedback, setFeedback] = useState(
"To continue, please allow Stud-E permission to location services"
);
async function requestLocation() {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status === "granted") {
getLocation();
return;
} else if (status === "denied") {
setFeedback("Stud-E requires location services to function");
setTimeout(() => {
startActivityAsync(ActivityAction.LOCATION_SOURCE_SETTINGS);
}, 3000);
console.log("Location Permission denied");
}
}
async function getLocation() {
let location = await Location.getCurrentPositionAsync({});
setLocation(location);
let dist = GetDistance(
location.coords.latitude,
location.coords.longitude,
8.4857,
124.6565
);
setDist(Math.round(dist));
}
useEffect(() => {
requestLocation();
}, []);
const ustpCoords = {
latitude: 8.4857,
longitude: 124.6565,
latitudeDelta: 0.000235,
longitudeDelta: 0.000067,
};
function CustomMap() {
if (dist !== null && location !== null) {
if (dist <= 1.5) {
// Just switch this condition for map debugging
return <MapView style={styles.map} initialRegion={ustpCoords} />;
} else {
return (
<View>
<Text style={styles.text_white_medium}>
You are too far from USTP {"\n"}
Get closer to use Stud-E
</Text>
<MapView
style={{
height: 256,
width: 256,
alignSelf: "center",
}}
showsUserLocation={true}
scrollEnabled={false}
zoomEnabled={false}
rotateEnabled={false}
followsUserLocation={true}
minZoomLevel={15}
initialRegion={{
latitude: location.coords.latitude,
longitude: location.coords.longitude,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
/>
<Text style={styles.text_white_small}>
{dist}km away from USTP {"\n"}
</Text>
</View>
);
}
} else {
return (
<AnimatedContainer>
<Text style={styles.text_white_medium}>{feedback}</Text>
<Button onPress={() => requestLocation()} color={colors.blue_3}>
<Text style={styles.text_white_small}>Allow Access</Text>
</Button>
</AnimatedContainer>
);
}
}
const creds = useSelector((state: RootState) => state.user.user);
return (
<View style={styles.background}>
<AnimatedContainer>
<Text style={styles.text_white_large}>Template Homepage</Text>
<Text style={styles.text_white_tiny}>{JSON.stringify(creds)}</Text>
<CustomMap />
</AnimatedContainer>
</View>
);

View file

@ -17,12 +17,17 @@ import { RootDrawerParamList } from "../../interfaces/Interfaces";
import { UserInfo, UserLogin } from "../../components/Api/Api";
import { ParseLoginError } from "../../components/ParseError/ParseError";
import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer";
import { setUser as setStateUser } from "../../features/redux/slices/AuthSlice/AuthSlice";
import { setUser } from "../../features/redux/slices/UserSlice/UserSlice";
import {
login,
setOnboarding,
unsetOnboarding,
} from "../../features/redux/slices/StatusSlice/StatusSlice";
export default function Login() {
const navigation = useNavigation<RootDrawerParamList>();
const dispatch = useDispatch();
const [user, setUser] = useState({
const [creds, setCreds] = useState({
username: "",
password: "",
error: "",
@ -49,11 +54,11 @@ export default function Login() {
placeholder="Username"
placeholderTextColor="white"
autoCapitalize="none"
value={user.username}
value={creds.username}
onChange={(
e: NativeSyntheticEvent<TextInputChangeEventData>
): void => {
setUser({ ...user, username: e.nativeEvent.text });
setCreds({ ...creds, username: e.nativeEvent.text });
}}
/>
<View style={{ paddingVertical: 4 }} />
@ -62,42 +67,43 @@ export default function Login() {
placeholder="Password"
placeholderTextColor="white"
secureTextEntry={true}
value={user.password}
value={creds.password}
onChange={(
e: NativeSyntheticEvent<TextInputChangeEventData>
): void => {
setUser({ ...user, password: e.nativeEvent.text });
setCreds({ ...creds, password: e.nativeEvent.text });
}}
/>
<View style={{ paddingVertical: 2 }} />
<Text style={styles.text_white_small}>{user.error}</Text>
<Text style={styles.text_white_small}>{creds.error}</Text>
<View style={{ paddingVertical: 4 }} />
<Button
onPress={async () => {
await UserLogin({
username: user.username,
password: user.password,
username: creds.username,
password: creds.password,
}).then(async (result) => {
if (result[0]) {
setUser({ ...user, username: "", password: "", error: "" });
setUser({ ...creds, username: "", password: "", error: "" });
let user_info = await UserInfo();
dispatch(setStateUser(user_info));
dispatch(login());
dispatch(setUser(user_info[1]));
// Redirect to onboarding if no year level, course, or semester specified
if (
!(
user_info.year_level ||
user_info.course ||
user_info.semester
)
user_info[1].year_level == null ||
user_info[1].course == null ||
user_info[1].semester == null
) {
dispatch(setOnboarding());
navigation.navigate("Onboarding");
} else {
dispatch(unsetOnboarding());
navigation.navigate("Home");
}
console.log(JSON.stringify(user_info));
} else {
setUser({
...user,
...creds,
error: ParseLoginError(JSON.stringify(result[1])),
});
}

View file

@ -2,46 +2,99 @@ import * as React from "react";
import styles from "../../styles";
import { View, Text } from "react-native";
import { useNavigation } from "@react-navigation/native";
import { RootDrawerParamList } from "../../interfaces/Interfaces";
import {
CourseParams,
RootDrawerParamList,
SemesterParams,
YearLevelParams,
} from "../../interfaces/Interfaces";
import { colors } from "../../styles";
import { AnimatePresence, MotiView } from "moti";
import { useEffect, useState } from "react";
import Button from "../../components/Button/Button";
import DropDownPicker from "react-native-dropdown-picker";
import isStringEmpty from "../../components/IsStringEmpty/IsStringEmpty";
import { useQuery } from "@tanstack/react-query";
import {
GetCourses,
GetSemesters,
GetYearLevels,
OnboardingUpdateStudentInfo,
} from "../../components/Api/Api";
import { useDispatch } from "react-redux";
import { unsetOnboarding } from "../../features/redux/slices/StatusSlice/StatusSlice";
import { setUser } from "../../features/redux/slices/UserSlice/UserSlice";
export default function Onboarding() {
const navigation = useNavigation<RootDrawerParamList>();
// const dispatch = useDispatch();
const dispatch = useDispatch();
// const creds = useSelector((state: RootState) => state.auth.creds);
const [student_info, setStudentInfo] = useState({
year_level: "",
course: "",
semester: "",
const [error, setError] = useState("");
// Semesters
const [selected_semester, setSelectedSemester] = useState("");
const [semesterOpen, setSemesterOpen] = useState(false);
const [semesters, setSemesters] = useState([
{ label: "1st Semester", value: "1st Sem" },
{ label: "2nd Semester", value: "2nd Sem" },
]);
const semester_query = useQuery({
queryKey: ["semesters"],
queryFn: GetSemesters,
onSuccess: (data) => {
let semesters = data.map((item: SemesterParams) => ({
label: item.name,
value: item.name,
}));
setSemesters(semesters);
},
});
function Introduction() {
const [shown, setShown] = useState(true);
useEffect(() => {
setTimeout(() => {
setShown(false);
}, 5000);
}, []);
// Year Level
const [selected_yearlevel, setSelectedYearLevel] = useState("");
const [yearLevelOpen, setYearLevelOpen] = useState(false);
const [year_levels, setYearLevels] = useState([
{ label: "1st Year", value: "1st Year" },
{ label: "2nd Year", value: "2nd Year" },
]);
const yearlevel_query = useQuery({
queryKey: ["year_levels"],
queryFn: GetYearLevels,
onSuccess: (data) => {
let year_levels = data.map((item: YearLevelParams) => ({
label: item.name,
value: item.name,
}));
setYearLevels(year_levels);
},
});
// Course
const [selected_course, setSelectedCourse] = useState("");
const [courseOpen, setCourseOpen] = useState(false);
const [courses, setCourses] = useState([
{
label: "Bachelor of Science in Information Technology",
value: "BSIT",
},
{ label: "Bachelor of Science in Computer Science", value: "BSCS" },
]);
const course_query = useQuery({
queryKey: ["courses"],
queryFn: GetCourses,
onSuccess: (data) => {
let courses = data.map((item: CourseParams) => ({
label: item.name,
value: item.name,
}));
setCourses(courses);
},
});
if (yearlevel_query.error || semester_query.error || course_query.error) {
return (
<AnimatePresence>
{shown && (
<MotiView
from={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
exitTransition={{ type: "timing", duration: 1200, delay: 600 }}
transition={{ type: "timing", duration: 1200, delay: 600 }}
>
<Text style={styles.text_white_small}>
We're glad to have you on board {"\n"}
Just a few more things before we get started
</Text>
</MotiView>
)}
</AnimatePresence>
<View style={styles.background}>
<View style={styles.container}>
<Text style={styles.text_white_medium}>Error loading details</Text>
</View>
</View>
);
}
return (
<View style={styles.background}>
<View style={styles.container}>
@ -63,14 +116,112 @@ export default function Onboarding() {
}}
/>
<View style={{ paddingVertical: 4 }} />
<Introduction />
<View style={{ paddingVertical: 8 }} />
<MotiView
from={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ type: "timing", duration: 900, delay: 2000 }}
transition={{ type: "timing", duration: 1200, delay: 600 }}
>
<Text style={styles.text_white_small}>
We're glad to have you on board {"\n"}
Just a few more things before we get started
</Text>
</MotiView>
<View style={{ paddingVertical: 8 }} />
<MotiView
from={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ type: "timing", duration: 400, delay: 1700 }}
>
<Text style={styles.text_white_medium}>Academic Info</Text>
<DropDownPicker
zIndex={3000}
open={courseOpen}
value={selected_course}
items={courses}
setOpen={(open) => {
setCourseOpen(open);
setSemesterOpen(false);
setYearLevelOpen(false);
}}
setValue={setSelectedCourse}
placeholder="Choose your course"
containerStyle={{
...styles.dropdown_template,
...{ zIndex: 3000 },
}}
dropDownContainerStyle={{ backgroundColor: "white" }}
/>
<DropDownPicker
zIndex={2000}
open={semesterOpen}
value={selected_semester}
items={semesters}
setOpen={(open) => {
setSemesterOpen(open);
setCourseOpen(false);
setYearLevelOpen(false);
}}
setValue={setSelectedSemester}
placeholder="Current semester"
containerStyle={{
...styles.dropdown_template,
...{ zIndex: 2000 },
}}
dropDownContainerStyle={{ backgroundColor: "white" }}
/>
<DropDownPicker
zIndex={1000}
open={yearLevelOpen}
value={selected_yearlevel}
items={year_levels}
setOpen={(open) => {
setYearLevelOpen(open);
setSemesterOpen(false);
setCourseOpen(false);
}}
setValue={setSelectedYearLevel}
placeholder="Your Year Level"
containerStyle={{
...styles.dropdown_template,
...{ zIndex: 1000 },
}}
dropDownContainerStyle={{ backgroundColor: "white" }}
/>
</MotiView>
<MotiView
from={{ opacity: 0 }}
animate={{ opacity: 1, zIndex: -1 }}
transition={{ type: "timing", duration: 400, delay: 1700 }}
style={styles.button_template}
>
<Text style={styles.text_white_small}>{error}</Text>
<Button
disabled={
!selected_yearlevel || !selected_course || !selected_semester
}
onPress={async () => {
let result = await OnboardingUpdateStudentInfo({
semester: selected_semester,
course: selected_course,
year_level: selected_yearlevel,
});
if (result[0]) {
dispatch(unsetOnboarding());
setSelectedCourse("");
setSelectedYearLevel("");
setSelectedSemester("");
setError("Success!");
dispatch(setUser(result[1]));
navigation.navigate("Home");
} else {
setError(result[1]);
}
}}
color={colors.blue_3}
>
<Text style={styles.text_white_small}>Proceed</Text>
</Button>
</MotiView>
</View>
</View>

View file

@ -160,7 +160,7 @@ export default function Register() {
} else {
setUser({
...user,
feedback: ParseError(JSON.stringify(result[2])),
feedback: ParseError(JSON.stringify(result[1])),
});
}
});

View file

@ -7,8 +7,14 @@ import { colors } from "../../styles";
import { useEffect, useState } from "react";
import { useNavigation } from "@react-navigation/native";
import { RootDrawerParamList } from "../../interfaces/Interfaces";
import { setUser } from "../../features/redux/slices/AuthSlice/AuthSlice";
import {
login,
unsetOnboarding,
} from "../../features/redux/slices/StatusSlice/StatusSlice";
import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer";
import { setUser } from "../../features/redux/slices/UserSlice/UserSlice";
import { setOnboarding } from "../../features/redux/slices/StatusSlice/StatusSlice";
import AsyncStorage from "@react-native-async-storage/async-storage";
export default function Revalidation() {
const dispatch = useDispatch();
@ -17,14 +23,23 @@ export default function Revalidation() {
useEffect(() => {
setState("Previous session found");
TokenRefresh().then(async (response) => {
if (response[0]) {
let user_info = await UserInfo();
await dispatch(setUser(user_info));
if (!(user_info.year_level || user_info.course || user_info.semester)) {
let user_info = await UserInfo();
if (response && user_info[0]) {
dispatch(login());
dispatch(setUser(user_info[1]));
if (
!(
user_info[1].year_level ||
user_info[1].course ||
user_info[1].semester
)
) {
dispatch(setOnboarding());
await setTimeout(() => {
navigation.navigate("Onboarding");
}, 700);
} else {
dispatch(unsetOnboarding());
await setTimeout(() => {
navigation.navigate("Home");
}, 700);
@ -32,6 +47,7 @@ export default function Revalidation() {
} else {
await setState("Session expired");
await setTimeout(() => {
AsyncStorage.clear();
navigation.navigate("Login");
}, 700);
}