From 4ce5a047143dbd62414b18cdb611566e9d47d028 Mon Sep 17 00:00:00 2001 From: Frank Nguyen <41023671+FrankreedX@users.noreply.github.com> Date: Thu, 15 Aug 2024 20:08:53 +0700 Subject: [PATCH] IWB-6: Added more loading spinners (#304) * added blacklist hook * black list feature works * some unused stuff removal * waitlist seems to work * simplified removeBlacklist.js * pretty * removed transaction and hope that it works * fixed useBlackList.js hook firestore error, and made blacklist check functional * added refresh spinner for chooseTeam.js * updated invalidateKeys and fixed some styling * updated faulty invalidateKeys lists * kinda works lmao * updated the invalidateKeys again * navigation off chooseTeam works. Tech debt +1 * half finished progress * invite works * added waitlistError to ErrorComponent * pretty * text input moves above keyboard now * minor comment change Co-authored-by: Jake Gehrke <91503842+Gehrkej@users.noreply.github.com> * changed console log wording in removeBlacklist Co-authored-by: Jake Gehrke <91503842+Gehrkej@users.noreply.github.com> * removed copied over comment * updated tiny comment Co-authored-by: Jake Gehrke <91503842+Gehrkej@users.noreply.github.com> * removed redundant log Co-authored-by: Jake Gehrke <91503842+Gehrkej@users.noreply.github.com> * fix - added email to blacklist record * fix - added assertion if pfp exists when removing a user * added loading spinner to all buttons * can't invite already invited email * accepting invite will remove the invite * workin on remove user * added invitelist bug fix * migrating to react native paper Button's loading property for loading spinner * redundant comment Co-authored-by: Jake Gehrke <91503842+Gehrkej@users.noreply.github.com> * :/ Co-authored-by: Jake Gehrke <91503842+Gehrkej@users.noreply.github.com> * prevent double navigation to chooseTeam screen * remove half baked loading spinner on dialog component (will be its own pr) * changed dialog component's signature to contain loading * fixed "invalid-argument" * added key * last usage of Activity Indicator in a simple button replaced temp * Fixed Android keyboard avoiding for invitelist * removed redundant remove user fix * pretty * pretty * pretty * pretty * pretty * pretty * IWB-7: allow using the "loading" property in DialogComponent (#313) * added missing state from merge * simple fix * better align invite input with above elements * changed more "white" to use themecolors, and added spinner color to some buttons --------- Co-authored-by: Jake Gehrke <91503842+Gehrkej@users.noreply.github.com> Co-authored-by: Jake Gehrke --- app/(auth)/signin.js | 6 +- app/(auth)/signup.js | 5 ++ app/content/assignments/players.js | 18 +---- app/content/profile/index.js | 22 +++--- app/content/team/index.js | 30 +++++--- app/content/team/users/[user]/index.js | 83 ++++++++++++--------- app/segments/(team)/blacklist.js | 6 ++ app/segments/(team)/chooseTeam.js | 17 ++++- app/segments/(team)/invitelist.js | 16 ++++ app/segments/(team)/waitlist.js | 75 +++++++++++++------ app/segments/drill/[id]/assignment/index.js | 39 ++++------ components/dialog.js | 12 +-- 12 files changed, 202 insertions(+), 127 deletions(-) diff --git a/app/(auth)/signin.js b/app/(auth)/signin.js index 58383fdf..fc20dbe7 100644 --- a/app/(auth)/signin.js +++ b/app/(auth)/signin.js @@ -33,6 +33,7 @@ export default function SignIn() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [forgotLoading, setForgotLoading] = useState(false); + const [loginLoading, setLoginLoading] = useState(false); const { setCurrentUserId } = useAuthContext(); const { height } = useWindowDimensions(); @@ -62,6 +63,7 @@ export default function SignIn() { }, [timeIntervalPassword]); async function handleSignIn() { + setLoginLoading(true); if (process.env.EXPO_PUBLIC_TEST_UID) { // Only allow login as test user while using `yarn test` to reduce errors setCurrentUserId(process.env.EXPO_PUBLIC_TEST_UID); @@ -74,6 +76,7 @@ export default function SignIn() { showDialog("Error", getErrorString(e)); } } + setLoginLoading(false); } async function handleForgotPassword() { @@ -204,6 +207,7 @@ export default function SignIn() { onPress={handleSignIn} buttonColor={themeColors.accent} labelStyle={styles.buttonText} + loading={loginLoading} > Login @@ -216,7 +220,7 @@ export default function SignIn() { style={styles.button} labelStyle={styles.buttonText} > - Sign Up + Sign up diff --git a/app/(auth)/signup.js b/app/(auth)/signup.js index 3fcef494..0287c6d4 100644 --- a/app/(auth)/signup.js +++ b/app/(auth)/signup.js @@ -36,11 +36,14 @@ export default function SignUp() { const [password, setPassword] = useState(""); const [passwordCheck, setPasswordCheck] = useState(""); + const [signUpLoading, setSignUpLoading] = useState(false); + const { showDialog, showSnackBar } = useAlertContext(); const { height } = useWindowDimensions(); async function handleSubmit() { + setSignUpLoading(true); try { if (password !== passwordCheck) { throw "Passwords don't match"; @@ -77,6 +80,7 @@ export default function SignUp() { console.log(e); showDialog("Error", getErrorString(e)); } + setSignUpLoading(false); } const styles = StyleSheet.create({ @@ -186,6 +190,7 @@ export default function SignUp() { onPress={handleSubmit} buttonColor={themeColors.accent} labelStyle={styles.buttonText} + loading={signUpLoading} > Submit diff --git a/app/content/assignments/players.js b/app/content/assignments/players.js index fe51c651..0ce34173 100644 --- a/app/content/assignments/players.js +++ b/app/content/assignments/players.js @@ -3,14 +3,7 @@ import { router, useLocalSearchParams, useNavigation } from "expo-router"; import { doc, getDoc, runTransaction, updateDoc } from "firebase/firestore"; import { useCallback, useEffect, useMemo, useState } from "react"; import { ScrollView, View } from "react-native"; -import { - ActivityIndicator, - Appbar, - Button, - Icon, - List, - Text, -} from "react-native-paper"; +import { Appbar, Button, Icon, List, Text } from "react-native-paper"; import { SafeAreaView } from "react-native-safe-area-context"; import { once } from "underscore"; import { themeColors } from "~/Constants"; @@ -409,7 +402,7 @@ function Index() { }} mode="contained" buttonColor={themeColors.accent} - textColor="white" + textColor={themeColors.highlight} disabled={ !assignmentList.some((assignment) => assignment.markedForDelete) } @@ -420,12 +413,9 @@ function Index() { }, }} onPress={handleDelete} + loading={loadingDelete} > - {loadingDelete ? ( - - ) : ( - "Delete" - )} + Delete )} diff --git a/app/content/profile/index.js b/app/content/profile/index.js index 0dfc2737..e6764a80 100644 --- a/app/content/profile/index.js +++ b/app/content/profile/index.js @@ -20,7 +20,7 @@ import { TouchableOpacity, View, } from "react-native"; -import { ActivityIndicator, Appbar, Switch } from "react-native-paper"; +import { ActivityIndicator, Appbar, Button, Switch } from "react-native-paper"; import { SafeAreaView } from "react-native-safe-area-context"; import { debounce } from "underscore"; import { themeColors } from "~/Constants"; @@ -267,11 +267,8 @@ function Index() { }, saveChangesButton: { backgroundColor: themeColors.accent, - paddingHorizontal: 20, - paddingVertical: 10, - borderRadius: 20, + width: 100, marginBottom: 20, - width: 100, // Increase the width of the button alignSelf: "center", }, saveChangesButtonText: { @@ -451,16 +448,17 @@ function Index() { )} {/* Save Button */} - - {updateLoading ? ( - - ) : ( - Update - )} - + Update + {/* Sign Out Button */} diff --git a/app/content/team/index.js b/app/content/team/index.js index 5c41c6b7..f29d6529 100644 --- a/app/content/team/index.js +++ b/app/content/team/index.js @@ -82,6 +82,7 @@ function Index() { const [resetDialogVisible, setResetDialogVisible] = useState(false); const hideResetDialog = () => setResetDialogVisible(false); + const [resetLoading, setResetLoading] = useState(false); const [newName, setNewName] = useState(""); @@ -222,18 +223,23 @@ function Index() { content="Resetting the season will wipe all leaderboards" visible={resetDialogVisible} onHide={hideResetDialog} - buttons={["Cancel", "Reset Season"]} - buttonsFunctions={[ - hideResetDialog, - async () => { - try { - await resetLeaderboards(currentTeamId); - await invalidateMultipleKeys(queryClient, [["best_attempts"]]); - hideResetDialog(); - } catch (e) { - console.log("Error resetting season:", e); - showDialog("Error", getErrorString(e)); - } + buttons={[ + { children: "Cancel", pressHandler: hideResetDialog }, + { + children: "Reset Season", + pressHandler: async () => { + setResetLoading(true); + try { + await resetLeaderboards(currentTeamId); + await invalidateMultipleKeys(queryClient, [["best_attempts"]]); + hideResetDialog(); + } catch (e) { + console.log("Error resetting season:", e); + showDialog("Error", getErrorString(e)); + } + setResetLoading(false); + }, + loading: resetLoading, }, ]} /> diff --git a/app/content/team/users/[user]/index.js b/app/content/team/users/[user]/index.js index a5c0ba64..4d934265 100644 --- a/app/content/team/users/[user]/index.js +++ b/app/content/team/users/[user]/index.js @@ -65,9 +65,11 @@ function Index() { const [removeDialogVisible, setRemoveDialogVisible] = useState(false); const hideRemoveDialog = () => setRemoveDialogVisible(false); + const [removeLoading, setRemoveLoading] = useState(false); const [banDialogVisible, setBanDialogVisible] = useState(false); const hideBanDialog = () => setBanDialogVisible(false); + const [banLoading, setBanLoading] = useState(false); const { showDialog, showSnackBar } = useAlertContext(); @@ -307,23 +309,31 @@ function Index() { content="All data will be lost when this user is removed." visible={removeDialogVisible} onHide={hideRemoveDialog} - buttons={["Cancel", "Remove User"]} - buttonsFunctions={[ - hideRemoveDialog, - async () => { - try { - await removeUser(currentTeamId, userId); - await queryClient.removeQueries(["userInfo", userId]); - await invalidateMultipleKeys(queryClient, [ - ["userInfo"], - ["best_attempts"], - ]); - navigation.goBack(); - } catch (e) { - console.log("Error removing user:", e); - hideRemoveDialog(); - showDialog("Error", getErrorString(e)); - } + buttons={[ + { + children: "Cancel", + pressHandler: hideRemoveDialog, + }, + { + children: "Remove User", + pressHandler: async () => { + setRemoveLoading(true); + try { + await removeUser(currentTeamId, userId); + await queryClient.removeQueries(["userInfo", userId]); + await invalidateMultipleKeys(queryClient, [ + ["userInfo"], + ["best_attempts"], + ]); + navigation.goBack(); + } catch (e) { + console.log("Error removing user:", e); + hideRemoveDialog(); + showDialog("Error", getErrorString(e)); + setRemoveLoading(false); + } + }, + loading: removeLoading, }, ]} /> @@ -333,23 +343,28 @@ function Index() { content="Banning this user will delete all their data and prevent them from joining the team again." visible={banDialogVisible} onHide={hideBanDialog} - buttons={["Cancel", "Ban User"]} - buttonsFunctions={[ - hideBanDialog, - async () => { - try { - await blacklistUser(currentTeamId, userId, userInfo, userEmail); - await queryClient.removeQueries(["userInfo", userId]); - await invalidateMultipleKeys(queryClient, [ - ["userInfo"], - ["best_attempts"], - ]); //invalidate cache - navigation.goBack(); - } catch (e) { - console.log("Error banning user:", e); - hideBanDialog(); - showDialog("Error", getErrorString(e)); - } + buttons={[ + { children: "Cancel", pressHandler: hideBanDialog }, + { + children: "Ban User", + pressHandler: async () => { + setBanLoading(true); + try { + await blacklistUser(currentTeamId, userId, userInfo, userEmail); + await queryClient.removeQueries(["userInfo", userId]); + await invalidateMultipleKeys(queryClient, [ + ["userInfo"], + ["best_attempts"], + ]); //invalidate cache + navigation.goBack(); + } catch (e) { + console.log("Error banning user:", e); + hideBanDialog(); + showDialog("Error", getErrorString(e)); + setBanLoading(false); + } + }, + loading: banLoading, }, ]} /> diff --git a/app/segments/(team)/blacklist.js b/app/segments/(team)/blacklist.js index d16eec3e..e4d54a6f 100644 --- a/app/segments/(team)/blacklist.js +++ b/app/segments/(team)/blacklist.js @@ -1,4 +1,5 @@ import { useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; import { ScrollView, View } from "react-native"; import { Button, List } from "react-native-paper"; import { themeColors } from "~/Constants"; @@ -21,6 +22,8 @@ function Blacklist() { const queryClient = useQueryClient(); // also called here for updating name + const [unbanLoading, setUnbanLoading] = useState({}); + const invalidateKeys = [["blacklist"]]; if (blacklistIsLoading) return ; @@ -52,10 +55,13 @@ function Blacklist() { > diff --git a/app/segments/(team)/chooseTeam.js b/app/segments/(team)/chooseTeam.js index 8b8b18e5..25c9e331 100644 --- a/app/segments/(team)/chooseTeam.js +++ b/app/segments/(team)/chooseTeam.js @@ -35,6 +35,9 @@ function ChooseTeam() { } = useAuthContext(); const queryClient = useQueryClient(); + const [buttonLoading, setButtonLoading] = useState(false); + const [signoutLoading, setSignoutLoading] = useState(false); + const { showDialog, showSnackBar } = useAlertContext(); const { @@ -83,6 +86,7 @@ function ChooseTeam() { ]; async function handleSignOut() { + setSignoutLoading(true); try { await signoutFireBase(auth); signOut(); @@ -90,6 +94,7 @@ function ChooseTeam() { console.log(e); showDialog("Error", getErrorString(e)); } + setSignoutLoading(false); } useEffect(() => { @@ -187,7 +192,7 @@ function ChooseTeam() { setLoading(false); }} loading={loading} - textColor="white" + textColor={themeColors.highlight} > @@ -143,6 +158,7 @@ export function Invitelist() { disabled={!currentEmailValid} onPress={onInvite} textColor={themeColors.accent} + loading={inviteLoading} > Invite diff --git a/app/segments/(team)/waitlist.js b/app/segments/(team)/waitlist.js index 012ec848..4b7fa43f 100644 --- a/app/segments/(team)/waitlist.js +++ b/app/segments/(team)/waitlist.js @@ -1,6 +1,8 @@ import { useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; import { ScrollView, View } from "react-native"; -import { Button, List } from "react-native-paper"; +import { ActivityIndicator, Button, List } from "react-native-paper"; +import { themeColors } from "~/Constants"; import { getErrorString } from "~/Utility"; import ErrorComponent from "~/components/errorComponent"; import Loading from "~/components/loading"; @@ -21,6 +23,9 @@ function Waitlist() { const { currentTeamId } = useAuthContext(); const queryClient = useQueryClient(); + //this is a pretty blunt implementation... I'm using the same variable here because each entry will disappear after either buttons are pressed + const [loading, setLoading] = useState({}); + if (waitlistError) { return ; } @@ -50,30 +55,56 @@ function Waitlist() { right={() => ( - - + {loading[userId] ? ( + + ) : ( + + + + + )} )} /> diff --git a/app/segments/drill/[id]/assignment/index.js b/app/segments/drill/[id]/assignment/index.js index 5ff8da71..ce1090f2 100644 --- a/app/segments/drill/[id]/assignment/index.js +++ b/app/segments/drill/[id]/assignment/index.js @@ -5,14 +5,7 @@ import { doc, runTransaction } from "firebase/firestore"; import { useCallback, useMemo, useState } from "react"; import { StyleSheet, TouchableOpacity, View } from "react-native"; import { ScrollView } from "react-native-gesture-handler"; -import { - ActivityIndicator, - Appbar, - Button, - List, - Text, - TouchableRipple, -} from "react-native-paper"; +import { Appbar, Button, List, Text } from "react-native-paper"; import { SafeAreaView } from "react-native-safe-area-context"; import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import { once } from "underscore"; @@ -237,31 +230,27 @@ export default function Index() { /> )} - - {loading ? ( - - ) : ( - - Assign - - )} - + Assign + ); } diff --git a/components/dialog.js b/components/dialog.js index 463b5ee4..3efa4725 100644 --- a/components/dialog.js +++ b/components/dialog.js @@ -16,10 +16,9 @@ export default function DialogComponent({ content, visible, onHide, - buttons = ["Ok"], - buttonsFunctions = [() => onHide()], + buttons = [{ children: "Ok", pressHandler: () => onHide(), loading: false }], //should I also include style in here? }) { - const Buttons = buttons.map((item, index) => { + const Buttons = buttons.map((button, index) => { let style; let labelStyle; if (index === 0) { @@ -27,17 +26,18 @@ export default function DialogComponent({ labelStyle = { color: themeColors.accent }; } else { style = { backgroundColor: themeColors.accent }; - labelStyle = { color: "white" }; + labelStyle = { color: themeColors.highlight }; } return ( ); });