Code improvements, clear react query cache on logout and added joining/changing study group functionality on homepage

This commit is contained in:
Keannu Bernasol 2023-09-29 12:23:44 +08:00
parent 709125a344
commit c2c589a3fe
4 changed files with 358 additions and 303 deletions

View file

@ -2,6 +2,7 @@ import axios from "axios";
import AsyncStorage from "@react-native-async-storage/async-storage"; import AsyncStorage from "@react-native-async-storage/async-storage";
import { import {
ActivationType, ActivationType,
LocationType,
LoginType, LoginType,
OnboardingType, OnboardingType,
PatchUserInfoType, PatchUserInfoType,
@ -87,7 +88,6 @@ export async function GetConfig() {
// User APIs // User APIs
export function UserRegister(register: RegistrationType) { export function UserRegister(register: RegistrationType) {
console.log(JSON.stringify(register));
return instance return instance
.post("/api/v1/accounts/users/", register) .post("/api/v1/accounts/users/", register)
.then(async (response) => { .then(async (response) => {
@ -148,7 +148,6 @@ export async function PatchUserInfo(info: PatchUserInfoType) {
return instance return instance
.patch("/api/v1/accounts/users/me/", info, config) .patch("/api/v1/accounts/users/me/", info, config)
.then((response) => { .then((response) => {
console.log(JSON.stringify(response.data));
return [true, response.data]; return [true, response.data];
}) })
.catch((error) => { .catch((error) => {
@ -255,7 +254,6 @@ export async function PatchStudentStatus(info: StudentStatusPatchType) {
}) })
.catch((error) => { .catch((error) => {
let error_message = ParseError(error); let error_message = ParseError(error);
console.log("Error!", error.response.data);
return [false, error_message]; return [false, error_message];
}); });
} }
@ -273,12 +271,11 @@ export async function GetStudentStatusList() {
}); });
} }
export async function GetStudentStatusListFiltered() { export async function GetStudentStatusListNear() {
const config = await GetConfig(); const config = await GetConfig();
return instance return instance
.get("/api/v1/student_status/filter/near_student_status", config) .get("/api/v1/student_status/near/", config)
.then((response) => { .then((response) => {
console.log("test", response.data);
return [true, response.data]; return [true, response.data];
}) })
.catch((error) => { .catch((error) => {
@ -288,10 +285,18 @@ export async function GetStudentStatusListFiltered() {
} }
// To-do // To-do
export async function GetStudentStatusListFilteredCurrentLocation() { export async function GetStudentStatusListFilteredCurrentLocation(
location: LocationType
) {
const config = await GetConfig(); const config = await GetConfig();
return instance return instance
.get("/api/v1/student_status/list/", config) .post(
"/api/v1/student_status/near_current_location/",
{
location: location,
},
config
)
.then((response) => { .then((response) => {
return [true, response.data]; return [true, response.data];
}) })
@ -329,7 +334,7 @@ export async function GetStudyGroupList() {
export async function CreateStudyGroup(info: StudyGroupCreateType) { export async function CreateStudyGroup(info: StudyGroupCreateType) {
const config = await GetConfig(); const config = await GetConfig();
console.log("Payload:", info); // console.log("Creating study group:", info);
return instance return instance
.post("/api/v1/study_groups/create/", info, config) .post("/api/v1/study_groups/create/", info, config)
.then((response) => { .then((response) => {

View file

@ -18,11 +18,13 @@ import { logout } from "../../features/redux/slices/StatusSlice/StatusSlice";
import AsyncStorage from "@react-native-async-storage/async-storage"; import AsyncStorage from "@react-native-async-storage/async-storage";
import UserIcon from "../../icons/UserIcon/UserIcon"; import UserIcon from "../../icons/UserIcon/UserIcon";
import SubjectIcon from "../../icons/SubjectIcon/SubjectIcon"; import SubjectIcon from "../../icons/SubjectIcon/SubjectIcon";
import { useQueryClient } from "@tanstack/react-query";
export default function CustomDrawerContent(props: {}) { export default function CustomDrawerContent(props: {}) {
const navigation = useNavigation<RootDrawerParamList>(); const navigation = useNavigation<RootDrawerParamList>();
const status = useSelector((state: RootState) => state.status); const status = useSelector((state: RootState) => state.status);
const dispatch = useDispatch(); const dispatch = useDispatch();
const queryClient = useQueryClient();
if (status.logged_in && status.onboarding) { if (status.logged_in && status.onboarding) {
return ( return (
<DrawerContentScrollView {...props}> <DrawerContentScrollView {...props}>
@ -40,6 +42,7 @@ export default function CustomDrawerContent(props: {}) {
onPress={async () => { onPress={async () => {
dispatch(logout()); dispatch(logout());
await AsyncStorage.clear(); await AsyncStorage.clear();
queryClient.clear();
navigation.navigate("Login"); navigation.navigate("Login");
}} }}
> >

View file

@ -3,31 +3,20 @@ import styles, { Viewport } from "../../styles";
import { import {
View, View,
Text, Text,
ToastAndroid,
TextInput, TextInput,
NativeSyntheticEvent, NativeSyntheticEvent,
TextInputChangeEventData, TextInputChangeEventData,
} from "react-native"; } from "react-native";
import { useState } from "react"; import { useState } from "react";
import { import {
UserInfoReturnType,
OptionType,
RootDrawerParamList, RootDrawerParamList,
StudentStatusType,
StudentStatusReturnType,
StudentStatusPatchType, StudentStatusPatchType,
StudyGroupCreateType, StudyGroupCreateType,
} from "../../interfaces/Interfaces"; } from "../../interfaces/Interfaces";
import Button from "../../components/Button/Button"; import Button from "../../components/Button/Button";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import { import { PatchStudentStatus, CreateStudyGroup } from "../../components/Api/Api";
PatchStudentStatus,
GetUserInfo,
ParseError,
CreateStudyGroup,
} from "../../components/Api/Api";
import { colors } from "../../styles"; import { colors } from "../../styles";
import DropDownPicker from "react-native-dropdown-picker";
import AnimatedContainerNoScroll from "../../components/AnimatedContainer/AnimatedContainerNoScroll"; import AnimatedContainerNoScroll from "../../components/AnimatedContainer/AnimatedContainerNoScroll";
import { urlProvider } from "../../components/Api/Api"; import { urlProvider } from "../../components/Api/Api";
import MapView, { UrlTile, Marker } from "react-native-maps"; import MapView, { UrlTile, Marker } from "react-native-maps";
@ -170,13 +159,6 @@ export default function CreateGroup({ route }: any) {
<View style={styles.padding} /> <View style={styles.padding} />
<Button <Button
onPress={() => { onPress={() => {
console.log({
group_name: name,
location: {
latitude: location.latitude,
longitude: location.longitude,
},
});
study_group_create.mutate({ study_group_create.mutate({
name: name, name: name,
location: location, location: location,

View file

@ -31,14 +31,11 @@ import {
} from "../../interfaces/Interfaces"; } from "../../interfaces/Interfaces";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { import {
CreateStudyGroup,
GetStudentStatus, GetStudentStatus,
GetStudentStatusList, GetStudentStatusListNear,
GetStudentStatusListFiltered,
GetStudyGroupList, GetStudyGroupList,
GetStudyGroupListFiltered, GetStudyGroupListFiltered,
PatchStudentStatus, PatchStudentStatus,
urlProvider,
} from "../../components/Api/Api"; } from "../../components/Api/Api";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useToast } from "react-native-toast-notifications"; import { useToast } from "react-native-toast-notifications";
@ -118,7 +115,7 @@ export default function Home() {
setDist(dist); setDist(dist);
// Deactivate student status if too far away // Deactivate student status if too far away
if (dist >= 2 && !map_debug) if (dist >= 2 && !map_debug)
mutation.mutate({ stop_studying.mutate({
active: false, active: false,
}); });
} }
@ -150,7 +147,6 @@ export default function Home() {
setButtonLabel("Start Studying"); setButtonLabel("Start Studying");
} }
setStudentStatus(data[1]); setStudentStatus(data[1]);
console.log(student_status);
}, },
onError: (error: Error) => { onError: (error: Error) => {
toast.show(String(error), { toast.show(String(error), {
@ -162,7 +158,7 @@ export default function Home() {
}, },
}); });
const mutation = useMutation({ const stop_studying = useMutation({
mutationFn: async (info: StudentStatusPatchType) => { mutationFn: async (info: StudentStatusPatchType) => {
const data = await PatchStudentStatus(info); const data = await PatchStudentStatus(info);
if (data[0] != true) { if (data[0] != true) {
@ -171,20 +167,70 @@ export default function Home() {
return data; return data;
}, },
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["user"] }); if (student_status?.study_group) {
queryClient.invalidateQueries({ queryKey: ["study_group_list_global"] }); // Display separate toast if you stop studying while in a study group
setTimeout(() => { toast.show("You left study group \n" + student_status?.study_group, {
queryClient.invalidateQueries({ queryKey: ["user_status"] }); type: "success",
queryClient.invalidateQueries({ queryKey: ["study_group_list"] }); placement: "top",
}, 500); duration: 2000,
setStudyGroups([]); animationType: "slide-in",
setStudying(false); });
}
toast.show("You are no longer studying \n" + subject, { toast.show("You are no longer studying \n" + subject, {
type: "success", type: "success",
placement: "top", placement: "top",
duration: 2000, duration: 2000,
animationType: "slide-in", animationType: "slide-in",
}); });
queryClient.invalidateQueries({ queryKey: ["user_status"] });
// Delay refetching for study groups since backend still needs to delete groups without students after leaving a study group
setTimeout(() => {
queryClient.invalidateQueries({ queryKey: ["study_group_list"] });
queryClient.invalidateQueries({
queryKey: ["study_group_list_global"],
});
}, 500);
setStudyGroups([]);
setStudying(false);
},
onError: (error: Error) => {
toast.show(String(error), {
type: "warning",
placement: "top",
duration: 2000,
animationType: "slide-in",
});
},
});
const change_study_group = useMutation({
mutationFn: async (info: StudentStatusPatchType) => {
const data = await PatchStudentStatus(info);
if (data[0] != true) {
return Promise.reject(new Error());
}
return data;
},
onSuccess: () => {
if (student_status?.study_group) {
// Display separate toast if you stop studying while in a study group
toast.show("You left study group \n" + student_status?.study_group, {
type: "success",
placement: "top",
duration: 2000,
animationType: "slide-in",
});
}
queryClient.invalidateQueries({ queryKey: ["user_status"] });
// Delay refetching for study groups since backend still needs to delete groups without students after leaving a study group
setTimeout(() => {
queryClient.invalidateQueries({ queryKey: ["study_group_list"] });
queryClient.invalidateQueries({
queryKey: ["study_group_list_global"],
});
}, 500);
setStudyGroups([]);
}, },
onError: (error: Error) => { onError: (error: Error) => {
toast.show(String(error), { toast.show(String(error), {
@ -203,7 +249,7 @@ export default function Home() {
enabled: studying, enabled: studying,
queryKey: ["user_status_list"], queryKey: ["user_status_list"],
queryFn: async () => { queryFn: async () => {
const data = await GetStudentStatusListFiltered(); const data = await GetStudentStatusListNear();
if (data[0] == false) { if (data[0] == false) {
return Promise.reject(new Error(JSON.stringify(data[1]))); return Promise.reject(new Error(JSON.stringify(data[1])));
} }
@ -259,7 +305,7 @@ export default function Home() {
>([]); >([]);
// Student Status List // Student Status List
const StudyGroupGlobalQuery = useQuery({ const StudyGroupGlobalQuery = useQuery({
enabled: !studying && !StudentStatusQuery.isFetching, enabled: !studying,
queryKey: ["study_group_list_global"], queryKey: ["study_group_list_global"],
queryFn: async () => { queryFn: async () => {
const data = await GetStudyGroupList(); const data = await GetStudyGroupList();
@ -367,276 +413,295 @@ export default function Home() {
); );
} }
)} )}
{study_groups.map((studygroup: StudyGroupType, index: number) => { {!StudyGroupQuery.isPaused &&
const randomColorWithOpacity = `rgba(${Math.floor( !StudyGroupQuery.isRefetching &&
Math.random() * 256 !StudyGroupQuery.error ? (
)}, ${Math.floor(Math.random() * 256)}, ${Math.floor( study_groups.map(
Math.random() * 256 (studygroup: StudyGroupType, index: number) => {
)}, 0.7)`; const randomColorWithOpacity = `rgba(${Math.floor(
Math.random() * 256
)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(
Math.random() * 256
)}, 0.7)`;
return ( return (
<React.Fragment key={index}> <React.Fragment key={index}>
<Marker <Marker
coordinate={studygroup.location} coordinate={studygroup.location}
pinColor={randomColorWithOpacity} pinColor={randomColorWithOpacity}
zIndex={1000} zIndex={1000}
onPress={() => { onPress={() => {
toast.hideAll(); toast.hideAll();
toast.show( toast.show(
<View <View
style={{ style={{
alignContent: "center", alignContent: "center",
alignSelf: "center", alignSelf: "center",
justifyContent: "center", justifyContent: "center",
}}
>
<Text style={styles.text_white_tiny_bold}>
Subject: {studygroup.subject}
</Text>
<Text style={styles.text_white_tiny_bold}>
Students Studying: {studygroup.students.length}
</Text>
<Button
onPress={() => {
mutation.mutate({
study_group: studygroup.name,
});
}}
>
<Text style={styles.text_white_tiny_bold}>
Join Group
</Text>
</Button>
</View>,
{
type: "normal",
placement: "top",
duration: 2000,
animationType: "slide-in",
style: {
backgroundColor: colors.secondary_2,
borderWidth: 1,
borderColor: colors.primary_1,
},
}
);
}}
/>
<Circle
center={studygroup.location}
radius={studygroup.radius}
fillColor={randomColorWithOpacity}
strokeColor="white"
zIndex={1000}
/>
</React.Fragment>
);
})}
{study_groups_global.map(
(studygroup: StudyGroupType, index: number) => {
const randomColorWithOpacity = `rgba(${Math.floor(
Math.random() * 256
)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(
Math.random() * 256
)}, 0.7)`;
return (
<React.Fragment key={index}>
<Marker
coordinate={studygroup.location}
pinColor={randomColorWithOpacity}
zIndex={1000}
onPress={() => {
toast.hideAll();
toast.show(
<View
style={{
alignContent: "center",
alignSelf: "center",
justifyContent: "center",
}}
>
<Text style={styles.text_white_tiny_bold}>
Subject: {studygroup.subject}
</Text>
<Text style={styles.text_white_tiny_bold}>
Students Studying: {studygroup.students.length}
</Text>
<Button
onPress={() => {
toast.show("Joined successfully", {
type: "success",
placement: "top",
duration: 2000,
animationType: "slide-in",
});
}} }}
> >
<Text style={styles.text_white_tiny_bold}> <Text style={styles.text_white_tiny_bold}>
Join Group Subject: {studygroup.subject}
</Text> </Text>
</Button> <Text style={styles.text_white_tiny_bold}>
</View>, Name: {studygroup.name}
{ </Text>
type: "normal", <Text style={styles.text_white_tiny_bold}>
placement: "top", Students Studying:{" "}
duration: 2000, {studygroup.students.length}
animationType: "slide-in", </Text>
style: { {student_status?.study_group !=
backgroundColor: colors.secondary_2, studygroup.name ? (
borderWidth: 1, <Button
borderColor: colors.primary_1, onPress={() => {
}, change_study_group.mutate({
} study_group: studygroup.name,
); });
}} }}
/> >
<Circle <Text style={styles.text_white_tiny_bold}>
center={studygroup.location} Join Group
radius={studygroup.radius} </Text>
fillColor={randomColorWithOpacity} </Button>
strokeColor="white" ) : (
zIndex={1000} <></>
/> )}
</React.Fragment> {student_status?.study_group ==
); studygroup.name ? (
} <Button
)} onPress={() => {
<Marker change_study_group.mutate({
zIndex={1001} study_group: "",
coordinate={{ });
latitude: }}
student_status?.location?.latitude || >
location.coords.latitude, <Text style={styles.text_white_tiny_bold}>
longitude: Leave Group
student_status?.location?.longitude || </Text>
location.coords.longitude, </Button>
}} ) : (
draggable={!student_status?.active} <></>
onDragEnd={(e) => { )}
const newLocation = e.nativeEvent.coordinate; </View>,
const distance = GetDistance( {
newLocation.latitude, type: "normal",
newLocation.longitude, placement: "top",
location.coords.latitude, duration: 2000,
location.coords.longitude animationType: "slide-in",
); style: {
if (distance <= 0.1) { backgroundColor: colors.secondary_2,
// If the new location is within 100 meters of the actual location, update the location state borderWidth: 1,
setLocation({ borderColor: colors.primary_1,
...location, },
coords: {
...location.coords,
latitude: newLocation.latitude,
longitude: newLocation.longitude,
},
});
} else {
// If the new location is more than 100 meters away from the actual location, reset the marker to the actual location
setLocation({
...location,
});
}
}}
pinColor={colors.primary_1}
onPress={() => {
toast.hideAll();
toast.show(
<View
style={{
alignContent: "center",
alignSelf: "center",
justifyContent: "center",
}}
>
<Text style={styles.text_white_tiny_bold}>
You are here
</Text>
<Text style={styles.text_white_tiny_bold}>
{"x: " +
(student_status?.location?.longitude != undefined
? student_status?.location?.longitude.toFixed(4)
: location.coords.longitude.toFixed(4))}
</Text>
<Text style={styles.text_white_tiny_bold}>
{"y: " +
(student_status?.location?.latitude != undefined
? student_status?.location?.latitude.toFixed(4)
: location.coords.latitude.toFixed(4))}
</Text>
{student_status?.active &&
!student_status?.study_group ? (
<>
<Text style={styles.text_white_tiny_bold}>
{student_status?.active
? "Studying " + student_status?.subject
: ""}
</Text>
<Button
onPress={() => {
if (student_status?.subject) {
console.log({
location: {
latitude: student_status?.location.latitude,
longitude:
student_status?.location.longitude,
},
subject: student_status?.subject,
});
navigation.navigate("Create Group", {
location: {
latitude: student_status?.location.latitude,
longitude:
student_status?.location.longitude,
},
subject: student_status?.subject,
});
} }
}} );
> }}
<Text style={styles.text_white_tiny_bold}> />
Create Group <Circle
</Text> center={studygroup.location}
</Button> radius={studygroup.radius}
</> fillColor={randomColorWithOpacity}
) : ( strokeColor="white"
<></> zIndex={1000}
)} />
{student_status?.study_group ? ( </React.Fragment>
<> );
<Text style={styles.text_white_tiny_bold}> }
{`Studying ${student_status?.subject}`} )
</Text> ) : (
<Text style={styles.text_white_tiny_bold}> <></>
{`In group ${student_status?.study_group}`} )}
</Text> {!studying ? (
</> study_groups_global.map(
) : ( (studygroup: StudyGroupType, index: number) => {
<></> const randomColorWithOpacity = `rgba(${Math.floor(
)} Math.random() * 256
</View>, )}, ${Math.floor(Math.random() * 256)}, ${Math.floor(
{ Math.random() * 256
type: "normal", )}, 0.7)`;
placement: "top",
duration: 2000, return (
animationType: "slide-in", <React.Fragment key={index}>
style: { <Marker
backgroundColor: colors.secondary_2, coordinate={studygroup.location}
borderWidth: 1, pinColor={randomColorWithOpacity}
borderColor: colors.primary_1, zIndex={1000}
}, onPress={() => {
toast.hideAll();
toast.show(
<View
style={{
alignContent: "center",
alignSelf: "center",
justifyContent: "center",
}}
>
<Text style={styles.text_white_tiny_bold}>
Subject: {studygroup.subject}
</Text>
<Text style={styles.text_white_tiny_bold}>
Name: {studygroup.name}
</Text>
<Text style={styles.text_white_tiny_bold}>
Students Studying:{" "}
{studygroup.students.length}
</Text>
{student_status?.study_group !=
studygroup.name ? (
<Text style={styles.text_white_tiny_bold}>
Get closer to join
</Text>
) : (
<></>
)}
</View>,
{
type: "normal",
placement: "top",
duration: 2000,
animationType: "slide-in",
style: {
backgroundColor: colors.secondary_2,
borderWidth: 1,
borderColor: colors.primary_1,
},
}
);
}}
/>
<Circle
center={studygroup.location}
radius={studygroup.radius}
fillColor={randomColorWithOpacity}
strokeColor="white"
zIndex={1000}
/>
</React.Fragment>
);
}
)
) : (
<></>
)}
{!studying || !student_status?.study_group ? (
<Marker
zIndex={1001}
coordinate={{
latitude:
student_status?.location?.latitude ||
location.coords.latitude,
longitude:
student_status?.location?.longitude ||
location.coords.longitude,
}}
draggable={!student_status?.active}
onDragEnd={(e) => {
const newLocation = e.nativeEvent.coordinate;
const distance = GetDistance(
newLocation.latitude,
newLocation.longitude,
location.coords.latitude,
location.coords.longitude
);
if (distance <= 0.1) {
// If the new location is within 100 meters of the actual location, update the location state
setLocation({
...location,
coords: {
...location.coords,
latitude: newLocation.latitude,
longitude: newLocation.longitude,
},
});
} else {
// If the new location is more than 100 meters away from the actual location, reset the marker to the actual location
setLocation({
...location,
});
} }
); }}
}} pinColor={colors.primary_1}
></Marker> onPress={() => {
toast.hideAll();
toast.show(
<View
style={{
alignContent: "center",
alignSelf: "center",
justifyContent: "center",
}}
>
<Text style={styles.text_white_tiny_bold}>
You are here
</Text>
{student_status?.active &&
!student_status?.study_group ? (
<>
<Text style={styles.text_white_tiny_bold}>
{student_status?.active
? "Studying " + student_status?.subject
: ""}
</Text>
<Button
onPress={() => {
if (student_status?.subject) {
navigation.navigate("Create Group", {
location: {
latitude:
student_status?.location.latitude,
longitude:
student_status?.location.longitude,
},
subject: student_status?.subject,
});
}
}}
>
<Text style={styles.text_white_tiny_bold}>
Create Group
</Text>
</Button>
</>
) : (
<></>
)}
{student_status?.study_group ? (
<>
<Text style={styles.text_white_tiny_bold}>
{`Studying: ${student_status?.subject}`}
</Text>
<Text style={styles.text_white_tiny_bold}>
{`In group: ${student_status?.study_group}`}
</Text>
</>
) : (
<></>
)}
</View>,
{
type: "normal",
placement: "top",
duration: 2000,
animationType: "slide-in",
style: {
backgroundColor: colors.secondary_2,
borderWidth: 1,
borderColor: colors.primary_1,
},
}
);
}}
></Marker>
) : (
<></>
)}
</MapView> </MapView>
<Button <Button
onPress={async () => { onPress={async () => {
if (!student_status?.active) { if (!student_status?.active) {
navigation.navigate("Start Studying", { location: location }); navigation.navigate("Start Studying", { location: location });
} else { } else {
mutation.mutate({ stop_studying.mutate({
active: false, active: false,
}); });
} }