Move to modals for user feedback

This commit is contained in:
Keannu Bernasol 2023-08-14 21:13:46 +08:00
parent 529a7a75fd
commit ff114b496c
10 changed files with 203 additions and 82 deletions

48
App.tsx
View file

@ -24,6 +24,8 @@ import UserInfoPage from "./src/routes/UserInfoPage/UserInfoPage";
import SubjectsPage from "./src/routes/SubjectsPage/SubjectsPage";
import Loading from "./src/routes/Loading/Loading";
import StartStudying from "./src/routes/StartStudying/StartStudying";
import { ToastProvider } from "react-native-toast-notifications";
import AppIcon from "./src/icons/AppIcon/AppIcon";
const Drawer = createDrawerNavigator();
@ -58,28 +60,30 @@ export default function App() {
}
}, [initialRoute]);
return (
<QueryClientProvider client={queryClient}>
<Provider store={store}>
<StatusBar style="light" />
<ToastProvider icon={<AppIcon size={64} />}>
<QueryClientProvider client={queryClient}>
<Provider store={store}>
<StatusBar style="light" />
<NavigationContainer linking={linking} fallback={<Loading />}>
<Drawer.Navigator
initialRouteName="Revalidation"
drawerContent={CustomDrawerContent}
screenOptions={DrawerScreenSettings}
>
<Drawer.Screen name="Home" component={Home} />
<Drawer.Screen name="Login" component={Login} />
<Drawer.Screen name="Register" component={Register} />
<Drawer.Screen name="Onboarding" component={Onboarding} />
<Drawer.Screen name="Revalidation" component={Revalidation} />
<Drawer.Screen name="Activation" component={Activation} />
<Drawer.Screen name="User Info" component={UserInfoPage} />
<Drawer.Screen name="Subjects" component={SubjectsPage} />
<Drawer.Screen name="Start Studying" component={StartStudying} />
</Drawer.Navigator>
</NavigationContainer>
</Provider>
</QueryClientProvider>
<NavigationContainer linking={linking} fallback={<Loading />}>
<Drawer.Navigator
initialRouteName="Revalidation"
drawerContent={CustomDrawerContent}
screenOptions={DrawerScreenSettings}
>
<Drawer.Screen name="Home" component={Home} />
<Drawer.Screen name="Login" component={Login} />
<Drawer.Screen name="Register" component={Register} />
<Drawer.Screen name="Onboarding" component={Onboarding} />
<Drawer.Screen name="Revalidation" component={Revalidation} />
<Drawer.Screen name="Activation" component={Activation} />
<Drawer.Screen name="User Info" component={UserInfoPage} />
<Drawer.Screen name="Subjects" component={SubjectsPage} />
<Drawer.Screen name="Start Studying" component={StartStudying} />
</Drawer.Navigator>
</NavigationContainer>
</Provider>
</QueryClientProvider>
</ToastProvider>
);
}

10
package-lock.json generated
View file

@ -36,6 +36,7 @@
"react-native-screens": "~3.20.0",
"react-native-select-dropdown": "^3.3.4",
"react-native-svg": "13.4.0",
"react-native-toast-notifications": "^3.3.1",
"react-query": "^3.39.3",
"react-redux": "^8.1.1",
"redux": "^4.2.1"
@ -12453,6 +12454,15 @@
"react-native": "*"
}
},
"node_modules/react-native-toast-notifications": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/react-native-toast-notifications/-/react-native-toast-notifications-3.3.1.tgz",
"integrity": "sha512-yc1Q2nOdIYvAf0GAIlmg8q42hiwpEHnLxkxJ6P+tN6jpcKZ1qzMXlgnmNdyF9cm9VOyHQexEP8952IKNAv1Olw==",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native/node_modules/promise": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz",

View file

@ -37,6 +37,7 @@
"react-native-screens": "~3.20.0",
"react-native-select-dropdown": "^3.3.4",
"react-native-svg": "13.4.0",
"react-native-toast-notifications": "^3.3.1",
"react-query": "^3.39.3",
"react-redux": "^8.1.1",
"redux": "^4.2.1"

View file

@ -18,6 +18,7 @@ import {
urlProvider,
} from "../../components/Api/Api";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useToast } from "react-native-toast-notifications";
export default function Home() {
// Switch this condition to see the main map when debugging
@ -29,6 +30,7 @@ export default function Home() {
"To continue, please allow Stud-E permission to location services"
);
const queryClient = useQueryClient();
const toast = useToast();
const ustpCoords = {
latitude: 8.4857,
@ -40,9 +42,14 @@ export default function Home() {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== "granted") {
setFeedback("Allow location permissions to continue");
ToastAndroid.show(
toast.show(
"Location permission was denied. Please allow in order to use StudE",
ToastAndroid.SHORT
{
type: "warning",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
}
);
return;
}
@ -122,16 +129,20 @@ export default function Home() {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["user"] });
queryClient.invalidateQueries({ queryKey: ["user_status"] });
ToastAndroid.show(
"You are no longer studying " + subject,
ToastAndroid.SHORT
);
toast.show("You are no longer studying " + subject, {
type: "success",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
},
onError: () => {
ToastAndroid.show(
"Server error. Unable to update student status",
ToastAndroid.SHORT
);
toast.show("Server error. Unable to update student status", {
type: "warning",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
},
});
function CustomCallout() {
@ -139,7 +150,7 @@ export default function Home() {
if (studying) {
return (
<Callout>
<Text style={styles.text_black_medium}>
<Text style={styles.text_black_tiny}>
You are here {"\n"}
X: {Math.round(location.coords.longitude) + "\n"}
Z: {Math.round(location.coords.latitude) + "\n"}

View file

@ -23,6 +23,7 @@ import {
setOnboarding,
unsetOnboarding,
} from "../../features/redux/slices/StatusSlice/StatusSlice";
import { useToast } from "react-native-toast-notifications";
export default function Login() {
const navigation = useNavigation<RootDrawerParamList>();
@ -31,7 +32,7 @@ export default function Login() {
username: "",
password: "",
});
const [error, setError] = useState("");
const toast = useToast();
return (
<View style={styles.background}>
<AnimatedContainer>
@ -66,8 +67,6 @@ export default function Login() {
setCreds({ ...creds, password: e.nativeEvent.text });
}}
/>
<View style={{ paddingVertical: 2 }} />
<Text style={styles.text_white_small}>{error}</Text>
<View style={{ paddingVertical: 4 }} />
<Button
onPress={async () => {
@ -88,14 +87,31 @@ export default function Login() {
) {
dispatch(setOnboarding());
navigation.navigate("Onboarding");
toast.show("Successfully logged in", {
type: "success",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
} else {
dispatch(unsetOnboarding());
toast.show("Successfully logged in", {
type: "success",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
navigation.navigate("Home");
}
console.log(JSON.stringify(user_info));
} else {
console.log(ParseLoginError(JSON.stringify(result[1])));
setError(ParseLoginError(JSON.stringify(result[1])));
toast.show(JSON.stringify(result[1]), {
type: "warning",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
}
});
}}

View file

@ -25,11 +25,12 @@ import { unsetOnboarding } from "../../features/redux/slices/StatusSlice/StatusS
import { setUser } from "../../features/redux/slices/UserSlice/UserSlice";
import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer";
import AnimatedContainerNoScroll from "../../components/AnimatedContainer/AnimatedContainerNoScroll";
import { useToast } from "react-native-toast-notifications";
export default function Onboarding() {
const navigation = useNavigation<RootDrawerParamList>();
const dispatch = useDispatch();
// const creds = useSelector((state: RootState) => state.auth.creds);
const [error, setError] = useState("");
const toast = useToast();
// Semesters
const [selected_semester, setSelectedSemester] = useState("");
const [semesterOpen, setSemesterOpen] = useState(false);
@ -196,7 +197,6 @@ export default function Onboarding() {
dropDownContainerStyle={{ backgroundColor: colors.primary_2 }}
/>
</MotiView>
<Text style={styles.text_white_small}>{error}</Text>
<MotiView
from={{
opacity: 0,
@ -227,11 +227,25 @@ export default function Onboarding() {
setSelectedCourse("");
setSelectedYearLevel("");
setSelectedSemester("");
setError("Success!");
dispatch(setUser(result[1]));
toast.show("Changes applied successfully", {
type: "success",
placement: "bottom",
duration: 6000,
animationType: "slide-in",
});
navigation.navigate("Home");
} else {
setError(result[1]);
dispatch(setUser(result[1]));
toast.show(
"An error has occured\nChanges have not been saved",
{
type: "warning",
placement: "bottom",
duration: 6000,
animationType: "slide-in",
}
);
}
}}
>

View file

@ -17,9 +17,11 @@ import { UserRegister } from "../../components/Api/Api";
import IsNumber from "../../components/IsNumber/IsNumber";
import ParseError from "../../components/ParseError/ParseError";
import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer";
import { useToast } from "react-native-toast-notifications";
export default function Register() {
const navigation = useNavigation<RootDrawerParamList>();
const toast = useToast();
// const dispatch = useDispatch();
// const creds = useSelector((state: RootState) => state.auth.creds);
const [user, setUser] = useState({
@ -29,7 +31,6 @@ export default function Register() {
username: "",
email: "",
password: "",
feedback: "",
});
return (
<View style={styles.background}>
@ -120,8 +121,6 @@ export default function Register() {
}}
/>
<View style={{ paddingVertical: 4 }} />
<Text style={styles.text_white_small}>{user.feedback}</Text>
<View style={{ paddingVertical: 4 }} />
<Button
onPress={async () => {
await UserRegister({
@ -142,16 +141,25 @@ export default function Register() {
username: "",
email: "",
password: "",
feedback:
"Success! An email has been sent to activate your account",
});
toast.show(
"Success! An email has been sent to activate your account",
{
type: "success",
placement: "bottom",
duration: 6000,
animationType: "slide-in",
}
);
setTimeout(() => {
navigation.navigate("Login");
}, 10000);
} else {
setUser({
...user,
feedback: ParseError(JSON.stringify(result[1])),
toast.show(JSON.stringify(result[1]), {
type: "warning",
placement: "bottom",
duration: 6000,
animationType: "slide-in",
});
}
});

View file

@ -15,11 +15,13 @@ import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContai
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";
import { useToast } from "react-native-toast-notifications";
export default function Revalidation() {
const dispatch = useDispatch();
const navigation = useNavigation<RootDrawerParamList>();
const [state, setState] = useState("Checking for existing session");
const toast = useToast();
useEffect(() => {
setState("Previous session found");
TokenRefresh().then(async (response) => {
@ -33,17 +35,35 @@ export default function Revalidation() {
!user_info[1].semester
) {
dispatch(setOnboarding());
toast.show("Previous session restored", {
type: "success",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
await setTimeout(() => {
navigation.navigate("Onboarding");
}, 700);
} else {
dispatch(unsetOnboarding());
toast.show("Previous session restored", {
type: "success",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
await setTimeout(() => {
navigation.navigate("Home");
}, 700);
}
} else {
await setState("Session expired");
toast.show("Session expired. Please login again", {
type: "warning",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
await setTimeout(() => {
AsyncStorage.clear();
navigation.navigate("Login");

View file

@ -16,11 +16,13 @@ import AnimatedContainerNoScroll from "../../components/AnimatedContainer/Animat
import { urlProvider } from "../../components/Api/Api";
import MapView, { UrlTile, Marker } from "react-native-maps";
import { useNavigation } from "@react-navigation/native";
import { useToast } from "react-native-toast-notifications";
export default function StartStudying({ route }: any) {
const { location } = route.params;
const queryClient = useQueryClient();
const navigation = useNavigation<RootDrawerParamList>();
const toast = useToast();
// Subject choices
const [selected_subject, setSelectedSubject] = useState("");
@ -37,10 +39,12 @@ export default function StartStudying({ route }: any) {
setSubjects(subjects);
},
onError: () => {
ToastAndroid.show(
"Server error: Unable to query available subjects",
ToastAndroid.SHORT
);
toast.show("Server error: Unable to query available subjects", {
type: "error",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
},
});
@ -49,17 +53,21 @@ export default function StartStudying({ route }: any) {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["user"] });
queryClient.invalidateQueries({ queryKey: ["user_status"] });
ToastAndroid.show(
"You are now studying " + selected_subject,
ToastAndroid.SHORT
);
toast.show("You are now studying " + selected_subject, {
type: "success",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
navigation.navigate("Home");
},
onError: () => {
ToastAndroid.show(
"A server error has occured. Please try again",
ToastAndroid.SHORT
);
toast.show("A server error has occured. Please try again", {
type: "error",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
},
});

View file

@ -42,12 +42,13 @@ import { useDispatch } from "react-redux";
import { setUser as setUserinState } from "../../features/redux/slices/UserSlice/UserSlice";
import * as ImagePicker from "expo-image-picker";
import * as FileSystem from "expo-file-system";
import { useToast } from "react-native-toast-notifications";
export default function UserInfoPage() {
const logged_in_user = useSelector((state: RootState) => state.user.user);
const dispatch = useDispatch();
const queryClient = useQueryClient();
const [feedback, setFeedback] = useState("");
const toast = useToast();
// Student Status
const studentstatus_mutation = useMutation({
@ -55,10 +56,20 @@ export default function UserInfoPage() {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["user"] });
queryClient.invalidateQueries({ queryKey: ["user_status"] });
setFeedback("Changes applied successfully");
toast.show("Changes applied successfully", {
type: "success",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
},
onError: () => {
setFeedback("An error has occured\nChanges have not been saved");
toast.show("An error has occured\nChanges have not been saved", {
type: "warning",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
},
});
@ -98,7 +109,12 @@ export default function UserInfoPage() {
dispatch(setUserinState(data[1]));
},
onError: () => {
setFeedback("Unable to query user info");
toast.show("Server Error: Unable to query user info", {
type: "warning",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
},
});
@ -111,11 +127,21 @@ export default function UserInfoPage() {
studentstatus_mutation.mutate({
active: false,
});
setFeedback("Changes applied successfully");
toast.show("Changes applied successfully", {
type: "success",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
dispatch(setUserinState(user));
},
onError: () => {
setFeedback("An error has occured\nChanges have not been saved");
toast.show("An error has occured\nChanges have not been saved", {
type: "warning",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
},
});
@ -136,7 +162,12 @@ export default function UserInfoPage() {
setSemesters(semestersData);
},
onError: () => {
setFeedback("Unable to query semester info");
toast.show("Server Error: Unable to query semester info", {
type: "warning",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
},
});
@ -155,7 +186,12 @@ export default function UserInfoPage() {
setYearLevels(year_levels);
},
onError: () => {
setFeedback("Unable to query year level info");
toast.show("Server Error: Unable to query year level info", {
type: "warning",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
},
});
@ -174,7 +210,12 @@ export default function UserInfoPage() {
setCourses(courses);
},
onError: () => {
setFeedback("Unable to query course info");
toast.show("Server Error: Unable to query course info", {
type: "warning",
placement: "bottom",
duration: 4000,
animationType: "slide-in",
});
},
});
@ -242,7 +283,6 @@ export default function UserInfoPage() {
e: NativeSyntheticEvent<TextInputChangeEventData>
): void => {
setUser({ ...user, first_name: e.nativeEvent.text });
setFeedback("");
}}
value={user.first_name}
/>
@ -259,7 +299,6 @@ export default function UserInfoPage() {
e: NativeSyntheticEvent<TextInputChangeEventData>
): void => {
setUser({ ...user, last_name: e.nativeEvent.text });
setFeedback("");
}}
value={user.last_name}
/>
@ -281,9 +320,6 @@ export default function UserInfoPage() {
setCourseOpen(false);
}}
setValue={setSelectedYearLevel}
onChangeValue={() => {
setFeedback("");
}}
placeholder={user.year_level}
placeholderStyle={{
...styles.text_white_tiny_bold,
@ -319,9 +355,6 @@ export default function UserInfoPage() {
setCourseOpen(false);
}}
setValue={setSelectedSemester}
onChangeValue={() => {
setFeedback("");
}}
placeholder={user.semester}
placeholderStyle={{
...styles.text_white_tiny_bold,
@ -357,9 +390,6 @@ export default function UserInfoPage() {
setCourseOpen(open);
}}
setValue={setSelectedCourse}
onChangeValue={() => {
setFeedback("");
}}
placeholder={user.course}
placeholderStyle={{
...styles.text_white_tiny_bold,
@ -412,7 +442,6 @@ export default function UserInfoPage() {
<Text style={styles.text_white_small}>Save Changes</Text>
</Button>
<View style={styles.padding} />
<Text style={styles.text_white_small}>{feedback}</Text>
</View>
</AnimatedContainerNoScroll>
</View>