react-query#useQueryCache TypeScript Examples

The following examples show how to use react-query#useQueryCache. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: TokensScreen.tsx    From vsinder with Apache License 2.0 5 votes vote down vote up
TokensScreen: React.FC<AuthStackNav<"tokens">> = ({
  route: {
    params: { accessToken, refreshToken },
  },
}) => {
  const cache = useQueryCache();
  const { refetch } = useQuery<MeResponse>("/me", { enabled: false });
  useEffect(() => {
    setTokens(accessToken, refreshToken);
    refetch().then((u) => {
      if (!u?.user || u.user.hasPushToken) {
        return;
      }
      AsyncStorage.getItem(key).then((x) => {
        if (x) {
          return;
        }

        registerForPushNotifications().then((pushToken) => {
          AsyncStorage.setItem(key, "true");
          if (!pushToken) {
            return;
          }

          defaultMutationFn([`/user/push-token`, { pushToken }, "PUT"]).then(
            () => {
              cache.setQueryData<MeResponse>(`/me`, (u) => {
                return {
                  user: {
                    ...u!.user!,
                    hasPushToken: true,
                  },
                };
              });
            }
          );
        });
      });
    });
  }, [refreshToken, accessToken, refetch]);
  return (
    <ScreenWrapper>
      <Center>
        <Loading />
      </Center>
    </ScreenWrapper>
  );
}
Example #2
Source File: MainTabStack.tsx    From vsinder with Apache License 2.0 5 votes vote down vote up
MainTabStack: React.FC<MainTabStackProps> = ({}) => {
  const { show } = useShowTabs();
  const {
    editorBackground,
    buttonBackground,
    buttonHoverBackground,
  } = useTheme();
  const cache = useQueryCache();

  useEffect(() => {
    const _handleAppStateChange = (nextAppState: AppStateStatus) => {
      if (nextAppState === "active") {
        cache.invalidateQueries(`/matches/0`);
        getSocket().reconnect();
      } else if (nextAppState === "background") {
        getSocket().close();
      }
    };

    AppState.addEventListener("change", _handleAppStateChange);

    return () => {
      AppState.removeEventListener("change", _handleAppStateChange);
    };
  }, []);

  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        tabBarIcon: ({ focused }) => {
          const size = 24;
          const color = focused ? buttonHoverBackground : buttonBackground;

          if (route.name === "swiper") {
            return <Entypo name="code" size={size} color={color} />;
          } else if (route.name === "profile") {
            return (
              <MaterialCommunityIcons
                name="account"
                size={size}
                color={color}
              />
            );
          } else if (route.name === "matches") {
            return <MessageIcon size={size} color={color} />;
          }

          return null;
        },
      })}
      swipeEnabled={false}
      tabBarOptions={{
        style: {
          height: show ? undefined : 0,
          backgroundColor: editorBackground,
        },
        indicatorStyle: {
          backgroundColor: buttonHoverBackground,
        },
        showIcon: true,
        showLabel: false,
      }}
      initialRouteName={"swiper"}
    >
      <Tab.Screen name="swiper" component={SwiperScreen} />
      <Tab.Screen name="matches" component={MatchesStack} />
      <Tab.Screen name="profile" component={ProfileStack} />
    </Tab.Navigator>
  );
}
Example #3
Source File: ReportUnMatchButton.tsx    From vsinder with Apache License 2.0 5 votes vote down vote up
ReportUnMatchButton: React.FC<ReportUnMatchButtonProps> = ({}) => {
  const { buttonBackground } = useTheme();
  const { showActionSheetWithOptions } = useActionSheet();
  const cache = useQueryCache();
  const navigation = useNavigation();
  const [mutate] = useMutation(defaultMutationFn, {
    onSuccess: () => {
      navigation.goBack();
      cache.setQueryData<MatchesResponse>("/matches/0", (m) => {
        return {
          matches: m?.matches.filter((x) => x.userId !== params.id) || [],
        };
      });
    },
  });
  const { params } = useRoute<MatchesStackNav<"messages">["route"]>();
  return (
    <ReportDialog
      onReportMessage={(message) => {
        mutate([
          "/report",
          { message, unmatchOrReject: "unmatch", userId: params.id },
          "POST",
        ]);
      }}
    >
      {(setOpen) => (
        <TouchableOpacity
          style={{
            flex: 1,
            flexDirection: "row",
            alignItems: "center",
            justifyContent: "center",
            paddingRight: 15,
          }}
          onPress={() => {
            const options = [
              "Report user",
              "Unmatch",
              "Report a bug",
              "Cancel",
            ];
            const destructiveButtonIndex = 0;
            const cancelButtonIndex = 3;

            showActionSheetWithOptions(
              {
                options,
                cancelButtonIndex,
                destructiveButtonIndex,
              },
              (buttonIndex) => {
                if (buttonIndex === 0) {
                  setOpen(true);
                } else if (buttonIndex === 1) {
                  mutate([`/unmatch`, { userId: params.id }, "POST"]);
                } else if (buttonIndex === 2) {
                  Linking.openURL("https://github.com/benawad/vsinder/issues");
                }
              }
            );
          }}
        >
          <MaterialIcons name="bug-report" size={27} color={buttonBackground} />
        </TouchableOpacity>
      )}
    </ReportDialog>
  );
}
Example #4
Source File: ReportUnMatchButton.tsx    From vsinder-app with Apache License 2.0 5 votes vote down vote up
ReportUnMatchButton: React.FC<ReportUnMatchButtonProps> = ({}) => {
  const { buttonBackground } = useTheme();
  const { showActionSheetWithOptions } = useActionSheet();
  const cache = useQueryCache();
  const navigation = useNavigation();
  const [mutate] = useMutation(defaultMutationFn, {
    onSuccess: () => {
      navigation.goBack();
      cache.setQueryData<MatchesResponse>("/matches/0", (m) => {
        return {
          matches: m?.matches.filter((x) => x.userId !== params.id) || [],
        };
      });
    },
  });
  const { params } = useRoute<MatchesStackNav<"messages">["route"]>();
  return (
    <ReportDialog
      onReportMessage={(message) => {
        mutate([
          "/report",
          { message, unmatchOrReject: "unmatch", userId: params.id },
          "POST",
        ]);
      }}
    >
      {(setOpen) => (
        <TouchableOpacity
          style={{
            flex: 1,
            flexDirection: "row",
            alignItems: "center",
            justifyContent: "center",
            paddingRight: 15,
          }}
          onPress={() => {
            const options = ["Report", "Unmatch", "Cancel"];
            const destructiveButtonIndex = 0;
            const cancelButtonIndex = 2;

            showActionSheetWithOptions(
              {
                options,
                cancelButtonIndex,
                destructiveButtonIndex,
              },
              (buttonIndex) => {
                if (buttonIndex === 0) {
                  setOpen(true);
                } else if (buttonIndex === 1) {
                  mutate([`/unmatch`, { userId: params.id }, "POST"]);
                }
              }
            );
          }}
        >
          <MaterialIcons name="bug-report" size={27} color={buttonBackground} />
        </TouchableOpacity>
      )}
    </ReportDialog>
  );
}
Example #5
Source File: MatchesScreen.tsx    From vsinder with Apache License 2.0 4 votes vote down vote up
MatchesScreen: React.FC<MatchesStackNav<"matchy">> = ({
  navigation,
}) => {
  const {
    buttonSecondaryHoverBackground,
    buttonSecondaryBackground,
  } = useTheme();
  const { data, isLoading } = useQuery<MatchesResponse>(
    "/matches/0",
    defaultQueryFn
  );

  const cache = useQueryCache();

  useOnWebSocket((e) => {
    if (e.type === "unmatch") {
      const d = cache.getQueryData<MatchesResponse>("/matches/0");
      if (d) {
        cache.setQueryData<MatchesResponse>("/matches/0", {
          matches: d.matches.filter((m) => m.userId !== e.userId),
        });
      }
    } else if (e.type === "new-match") {
      cache.invalidateQueries("/matches/0");
    } else if (e.type === "new-like") {
      cache.setQueryData<MeResponse>("/me", (u) => {
        return {
          user: {
            ...u!.user!,
            numLikes: u!.user!.numLikes + 1,
          },
        };
      });
    } else if (e.type === "new-message") {
      const state = navigation.dangerouslyGetState();
      const route: any = state.routes[state.index];
      const read = !!(route && route.params?.userId === e.message.senderId);
      const d = cache.getQueryData<MatchesResponse>("/matches/0");
      if (d) {
        cache.setQueryData<MatchesResponse>("/matches/0", {
          matches: d.matches.map((m) =>
            m.userId === e.message.senderId
              ? {
                  ...m,
                  read,
                  message: {
                    text: e.message.text,
                    createdAt: e.message.createdAt,
                  },
                }
              : m
          ),
        });
      }
    }
  });

  useEffect(() => {
    if (data?.matches) {
      const d0 = cache.getQueryData<MeResponse>("/me");
      if (!d0 || !d0.user) {
        return;
      }
      const newUnreadMatchUserIds: Array<{
        userId1: string;
        userId2: string;
      }> = [];
      data.matches.forEach((m) => {
        if (!m.read) {
          const [u1, u2] = [m.userId, d0.user!.id].sort();
          newUnreadMatchUserIds.push({ userId1: u1, userId2: u2 });
        }
      });
      cache.setQueryData<MeResponse>("/me", {
        user: {
          ...d0.user,
          unreadMatchUserIds: newUnreadMatchUserIds,
        },
      });
    }
  }, [data?.matches]);

  const isClearing = useRef(false);
  useFocusEffect(() => {
    getPresentedNotificationsAsync().then((x) => {
      if (x.length && !isClearing.current) {
        isClearing.current = true;
        dismissAllNotificationsAsync().finally(() => {
          isClearing.current = false;
        });
      }
    });
  });

  if (isLoading) {
    return <FullscreenLoading />;
  }

  if (!data?.matches.length) {
    return <FullscreenMessage message="No matches yet ?" />;
  }

  return (
    <ScreenWrapper noPadding>
      <FlatList
        showsVerticalScrollIndicator={false}
        style={{ paddingTop: 16 }}
        data={data.matches.sort(compareMatches)}
        keyExtractor={(x) => x.userId}
        renderItem={({ item }) => (
          <TouchableOpacity
            onPress={() =>
              navigation.navigate("messages", { ...item, id: item.userId })
            }
            style={{
              flexDirection: "row",
              alignItems: "center",
              backgroundColor: item.read
                ? undefined
                : buttonSecondaryHoverBackground,
              padding: 15,
              borderBottomColor: buttonSecondaryBackground,
              borderBottomWidth: 1,
            }}
          >
            <TouchableOpacity
              style={{ paddingRight: 12 }}
              onPress={() =>
                navigation.navigate(`viewCard`, { id: item.userId })
              }
            >
              <Avatar src={item.photoUrl} />
            </TouchableOpacity>
            <View style={{ flex: 1 }}>
              <View
                style={{
                  alignItems: "center",
                  flexDirection: "row",
                  width: "100%",
                }}
              >
                <MyText
                  style={{ fontSize: 20, marginRight: 4 }}
                  numberOfLines={1}
                >
                  {item.displayName}
                </MyText>
                <Flair name={item.flair as any} />
                <MyText style={{ marginLeft: "auto" }} numberOfLines={1}>
                  {dtToHumanStr(
                    new Date(
                      item.message ? item.message.createdAt : item.createdAt
                    )
                  )}
                </MyText>
              </View>
              <MyText style={{ marginTop: 4 }}>
                {item.message?.text || " "}
              </MyText>
            </View>
          </TouchableOpacity>
        )}
      />
    </ScreenWrapper>
  );
}
Example #6
Source File: MessagesScreen.tsx    From vsinder with Apache License 2.0 4 votes vote down vote up
MessagesScreen: React.FC<MatchesStackNav<"messages">> = ({
  route: { params },
  navigation,
}) => {
  const qKey = `/messages/${params.id}`;
  const {
    data,
    isLoading,
    isFetchingMore,
    fetchMore,
  } = useInfiniteQuery<IMessageResponse>(
    qKey,
    (key, cursor = "") =>
      defaultQueryFn(`${key}/${cursor}`).then((x) => ({
        hasMore: x.hasMore,
        messages: x.messages.map((m: Message) =>
          messageToGiftedMessage(m, { me: meData!.user!, them: params })
        ),
      })),
    {
      staleTime: 0,
      getFetchMore: ({ messages, hasMore }) =>
        hasMore && messages.length
          ? messages[messages.length - 1].createdAt
          : "",
    }
  );
  const { data: meData } = useQuery<MeResponse>("/me", defaultQueryFn);
  const cache = useQueryCache();
  const [mutate] = useMutation(defaultMutationFn);
  const {
    inputForeground,
    inputBackground,
    buttonBackground,
    buttonForeground,
    buttonSecondaryBackground,
    buttonSecondaryForeground,
    buttonForegroundDarker,
    buttonSecondaryForegroundDarker,
  } = useTheme();

  useOnWebSocket((e) => {
    if (e.type === "new-message" && e.message.senderId === params.id) {
      cache.setQueryData<IMessageResponse[]>(qKey, (d) => {
        return produce(d!, (x) => {
          x[0].messages = GiftedChat.append(x[0].messages, [
            messageToGiftedMessage(e.message, {
              me: meData!.user!,
              them: params,
            }),
          ]);
        });
      });
    } else if (e.type === "unmatch") {
      if (e.userId === params.id) {
        navigation.goBack();
      }
    }
  });

  useEffect(() => {
    getSocket().send(
      JSON.stringify({ type: "message-open", userId: params.id })
    );
    const d = cache.getQueryData<MatchesResponse>("/matches/0");
    if (d && d.matches.find((x) => x.userId === params.id && !x.read)) {
      cache.setQueryData<MatchesResponse>("/matches/0", {
        matches: d.matches.map((m) =>
          m.userId === params.id ? { ...m, read: true } : m
        ),
      });
    }

    return () => {
      getSocket().send(JSON.stringify({ type: "message-open", userId: null }));
    };
  }, []);

  if (isLoading) {
    return <FullscreenLoading />;
  }

  if (!meData?.user) {
    return null;
  }

  const messages = data ? data.map((x) => x.messages).flat() : [];

  return (
    <ScreenWrapper noPadding>
      <GiftedChat
        alignTop
        loadEarlier={data?.[data?.length - 1]?.hasMore}
        onPressAvatar={(u) =>
          navigation.navigate("viewCard", { id: u._id.toString() })
        }
        isLoadingEarlier={!!isFetchingMore}
        renderLoadEarlier={({ isLoadingEarlier }) =>
          isLoadingEarlier ? (
            <Loading />
          ) : (
            <MyButton onPress={() => fetchMore()}>load more</MyButton>
          )
        }
        listViewProps={{
          showsVerticalScrollIndicator: false,
        }}
        timeTextStyle={{
          left: { color: buttonSecondaryForegroundDarker },
          right: { color: buttonForegroundDarker },
        }}
        renderBubble={(props) => {
          return (
            <Bubble
              {...props}
              textStyle={{
                right: {
                  color: buttonForeground,
                },
                left: {
                  color: buttonSecondaryForeground,
                },
              }}
              wrapperStyle={{
                left: {
                  backgroundColor: buttonSecondaryBackground,
                },
                right: {
                  backgroundColor: buttonBackground,
                },
              }}
            />
          );
        }}
        renderSend={(props) => (
          <Send
            {...props}
            containerStyle={{
              justifyContent: "center",
              alignItems: "center",
              alignSelf: "center",
              marginRight: 15,
            }}
          >
            <MaterialIcons name="send" size={24} color={buttonBackground} />
          </Send>
        )}
        // @ts-ignore
        containerStyle={{
          backgroundColor: inputBackground,
        }}
        textInputStyle={{
          color: inputForeground,
          backgroundColor: inputBackground,
        }}
        // @ts-ignore
        renderTicks={() => null}
        messages={messages}
        onSend={(messages) => {
          messages.forEach((m) => {
            mutate([
              "/message",
              { recipientId: params.id, text: m.text, matchId: params.matchId },
              "POST",
            ]).then(({ message: newMessage }) => {
              cache.setQueryData<IMessageResponse[]>(qKey, (d) => {
                return produce(d!, (x) => {
                  x[0].messages = GiftedChat.append(x[0].messages, [
                    messageToGiftedMessage(newMessage, {
                      me: meData.user!,
                      them: params,
                    }),
                  ]);
                });
              });
              const d = cache.getQueryData<MatchesResponse>("/matches/0");
              if (d) {
                cache.setQueryData<MatchesResponse>("/matches/0", {
                  matches: d.matches.map((m) =>
                    m.userId === params.id
                      ? {
                          ...m,
                          message: {
                            text: newMessage.text,
                            createdAt: newMessage.createdAt,
                          },
                        }
                      : m
                  ),
                });
              }
            });
          });
        }}
        user={{
          _id: meData.user.id,
        }}
      />
    </ScreenWrapper>
  );
}
Example #7
Source File: EditProfile.tsx    From vsinder with Apache License 2.0 4 votes vote down vote up
EditProfile: React.FC<ProfileStackNav<"editProfile">> = ({
  navigation,
}) => {
  const scrollView = useRef<ScrollView>(null);
  const { data, isLoading } = useQuery<MeResponse>("/me");
  const cache = useQueryCache();
  const [mutate, { isLoading: mutIsLoading }] = useMutation(defaultMutationFn);

  if (isLoading) {
    return <FullscreenLoading />;
  }

  return (
    <ScreenWrapper>
      <Formik
        initialValues={
          data?.user ? userToInitialFormData(data.user) : initialProfileData
        }
        validateOnChange={false}
        validateOnBlur={false}
        validationSchema={schema}
        onSubmit={async ({ sendNotifs, ...values }) => {
          const d: any = {
            ...values,
            ageRangeMax: parseInt(values.ageRangeMax),
            ageRangeMin: parseInt(values.ageRangeMin),
            birthday: format(values.birthday, "yyyy-MM-dd"),
          };

          if (data!.user!.hasPushToken !== sendNotifs) {
            if (sendNotifs) {
              const t = await registerForPushNotifications();
              if (t) {
                d.pushToken = t;
              }
            } else {
              d.pushToken = null;
            }
          }
          try {
            const { user } = await mutate(["/user", d, "PUT"]);
            cache.setQueryData("/me", { user });
            cache.invalidateQueries("/feed");
            cache.invalidateQueries("/matches/0");
            if (data!.user!.codeImgIds.length) {
              navigation.goBack();
            } else {
              if (useCodeImgs.getState().codeImgs.length) {
                navigation.navigate(`manageCodePics`);
              } else {
                navigation.navigate(`codeSnippeter`, { replace: true });
              }
            }
          } catch {}
        }}
      >
        {({ values, setFieldValue, handleSubmit, errors }) => {
          const age = getAge(values.birthday);
          const tooYoung = age < 18 && Platform.OS === "ios";
          return (
            <KeyboardAwareScrollView
              ref={scrollView}
              showsVerticalScrollIndicator={false}
              keyboardShouldPersistTaps="handled"
            >
              <FormSpacer>
                <TextField name="displayName" label="Name" />
              </FormSpacer>
              <FormSpacer>
                <TextField name="bio" label="Bio" multiline />
              </FormSpacer>
              <FormSpacer>
                <FlairSelectField
                  name="flair"
                  label={
                    values.flair ? (
                      <Flair name={values.flair as any} />
                    ) : (
                      "Flair"
                    )
                  }
                />
              </FormSpacer>
              <FormSpacer>
                <DatePickerField
                  name="birthday"
                  label={`Birthday (used to compute age: ${age})`}
                />
                {tooYoung ? (
                  <ErrorText>
                    You need to be 18 or older to use the app.
                  </ErrorText>
                ) : null}
              </FormSpacer>
              <FormSpacer>
                <RadioGroupField
                  name="goal"
                  label="I'm looking for:"
                  onValue={(v) => {
                    if (v === "friendship") {
                      values.ageRangeMin = initialProfileData.ageRangeMin;
                      values.ageRangeMax = initialProfileData.ageRangeMax;
                    }
                  }}
                  options={[
                    ...(age >= 18
                      ? [
                          {
                            label: "Love",
                            value: "love",
                          },
                        ]
                      : []),
                    { label: "Friendship", value: "friendship" },
                  ]}
                />
              </FormSpacer>
              {values.goal === "love" ? (
                <>
                  <FormSpacer>
                    <RadioGroupField
                      name="gender"
                      label="Gender:"
                      options={[
                        { label: "Male", value: "male" },
                        { label: "Female", value: "female" },
                        { label: "Non-binary", value: "non-binary" },
                      ]}
                    />
                  </FormSpacer>
                  <FormSpacer>
                    <CheckboxGroupField
                      name="gendersToShow"
                      label="Show me code from:"
                      options={[
                        { label: "Males", value: "male" },
                        { label: "Females", value: "female" },
                        { label: "Non-binary", value: "non-binary" },
                      ]}
                    />
                  </FormSpacer>
                  <FormSpacer>
                    <View style={{ marginBottom: 8 }}>
                      <Label>Age Range: </Label>
                    </View>
                    <View style={{ flexDirection: "row" }}>
                      <MyTextInput
                        keyboardType="numeric"
                        value={values.ageRangeMin}
                        onChangeText={(x) => setFieldValue("ageRangeMin", x)}
                      />
                      <MyTextInput
                        style={{ marginLeft: 16 }}
                        keyboardType="numeric"
                        value={"" + values.ageRangeMax}
                        onChangeText={(x) => setFieldValue("ageRangeMax", x)}
                      />
                    </View>
                    {errors.ageRangeMax ? (
                      <ErrorText style={{ marginTop: 8 }}>
                        {errors.ageRangeMax}
                      </ErrorText>
                    ) : null}
                    {errors.ageRangeMin ? (
                      <ErrorText style={{ marginTop: 4 }}>
                        {errors.ageRangeMin}
                      </ErrorText>
                    ) : null}
                  </FormSpacer>
                </>
              ) : null}
              <FormSpacer>
                <View style={{ marginBottom: 4 }}>
                  <Label>Notifications: </Label>
                </View>
                <CheckboxWithLabel
                  checked={values.sendNotifs}
                  onPress={() =>
                    setFieldValue("sendNotifs", !values.sendNotifs)
                  }
                  label="Send me them on new matches/messages"
                />
              </FormSpacer>
              <View style={{ paddingTop: 10 }}>
                {tooYoung ? null : (
                  <LoadingButton
                    isLoading={mutIsLoading}
                    onPress={() => handleSubmit()}
                  >
                    {data!.user!.codeImgIds.length ? "Save" : "Next"}
                  </LoadingButton>
                )}
              </View>
              <View style={{ height: 30 }} />
            </KeyboardAwareScrollView>
          );
        }}
      </Formik>
    </ScreenWrapper>
  );
}
Example #8
Source File: ManageCodePics.tsx    From vsinder with Apache License 2.0 4 votes vote down vote up
ManageCodePics: React.FC<ProfileStackNav<"manageCodePics">> = ({
  navigation,
}) => {
  const [{ open, start }, setOpen] = useState(closed);
  const { showActionSheetWithOptions } = useActionSheet();
  const { codeImgs, set } = useCodeImgs();
  const cache = useQueryCache();
  const [mutate, { isLoading }] = useMutation(defaultMutationFn);
  const { inputBackground, inputForeground } = useTheme();
  const loadingSomeImgs = codeImgs.some((x) => !x.value);
  return (
    <ScreenWrapper>
      <SelectModal
        isVisible={open}
        onBackButtonPress={() => setOpen(closed)}
        onBackdropPress={() => setOpen(closed)}
        options={codeImgs.map((_, i) => ({ value: i, label: "" + i }))}
        onItem={(item) => {
          const newList = [...codeImgs];
          const target = item.value as number;

          if (start < target) {
            newList.splice(target + 1, 0, newList[start]);
            newList.splice(start, 1);
          } else {
            newList.splice(target, 0, newList[start]);
            newList.splice(start + 1, 1);
          }
          set({ codeImgs: newList });
          setOpen(closed);
        }}
      />
      <ScrollView showsVerticalScrollIndicator={false}>
        {codeImgs.map(({ value, tmpId }, i) =>
          !value ? (
            <View
              key={tmpId}
              style={[
                styles.card,
                {
                  borderRadius: 9,
                  padding: 16,
                  backgroundColor: inputBackground,
                  alignItems: "center",
                  justifyContent: "center",
                  height: 200,
                },
              ]}
            >
              <MyText
                style={{
                  color: inputForeground,
                  fontSize: 18,
                  marginBottom: 20,
                }}
              >
                generating image
              </MyText>
              <Loading />
            </View>
          ) : (
            <TouchableOpacity
              key={tmpId}
              style={styles.card}
              onPress={() => {
                const options = ["Delete", "Move", "Cancel"];
                const destructiveButtonIndex = 0;
                const cancelButtonIndex = 2;
                showActionSheetWithOptions(
                  {
                    options,
                    cancelButtonIndex,
                    destructiveButtonIndex,
                  },
                  (buttonIndex) => {
                    if (buttonIndex === 0) {
                      set({
                        codeImgs: codeImgs.filter((x) => x.tmpId !== tmpId),
                      });
                    } else if (buttonIndex === 1) {
                      setOpen({ open: true, start: i });
                    }
                  }
                );
              }}
            >
              <CodeImage id={value} />
            </TouchableOpacity>
          )
        )}
      </ScrollView>
      <View style={{ height: 20 }} />
      <LoadingButton
        disabled={loadingSomeImgs}
        isLoading={isLoading}
        onPress={async () => {
          if (!codeImgs.length) {
            showMessage({
              message: "You need to add at least 1 code image",
              duration: 10000,
              type: "danger",
            });
            return;
          }
          try {
            const ids = codeImgs.map((x) => x.value);
            await mutate(["/user/imgs", { codeImgIds: ids }, "PUT"]);
            const d = cache.getQueryData<MeResponse>("/me");
            if (d) {
              cache.setQueryData("/me", {
                user: { ...d.user, codeImgIds: ids },
              });
            }
            if (navigation.canGoBack()) {
              navigation.goBack();
            }
            // navigation.reset({ routes: [{ name: "viewProfile" }] });
          } catch {}
        }}
      >
        {loadingSomeImgs ? "Wait while images are getting generated" : "Save"}
      </LoadingButton>
    </ScreenWrapper>
  );
}
Example #9
Source File: Settings.tsx    From vsinder with Apache License 2.0 4 votes vote down vote up
Settings: React.FC<ProfileStackNav<"settings">> = ({
  navigation,
}) => {
  const [status, setStatus] = React.useState<"loading" | "">("");
  const [mutate, { isLoading }] = useMutation(defaultMutationFn);
  const cache = useQueryCache();

  useEffect(() => {
    if (status === "loading") {
      Updates.fetchUpdateAsync()
        .then(() => {
          Updates.reloadAsync();
        })
        .catch((e) => {
          setStatus("");
          showMessage({
            message: (e as Error).message,
            duration: 10000,
            type: "danger",
          });
        });
    }
  }, [status]);

  if (isLoading || status === "loading") {
    return <FullscreenLoading />;
  }

  return (
    <ScreenWrapper noPadding>
      <ScrollView alwaysBounceVertical={false}>
        <Cell onPress={() => navigation.navigate("changeTheme")}>
          Change theme
        </Cell>
        <Cell
          onPress={async () => {
            try {
              const update = await Updates.checkForUpdateAsync();
              if (update.isAvailable) {
                Alert.alert(
                  "Update Available",
                  "Would you like to update now?",
                  [
                    {
                      text: "Cancel",
                      style: "cancel",
                    },
                    {
                      text: "OK",
                      onPress: () => {
                        setStatus("loading");
                      },
                    },
                  ],
                  { cancelable: false }
                );
              } else {
                Alert.alert(
                  "You're on the latest version",
                  "",
                  [
                    {
                      text: "OK",
                      onPress: () => {},
                    },
                  ],
                  { cancelable: false }
                );
              }
            } catch (e) {
              showMessage({
                message: (e as Error).message,
                duration: 10000,
                type: "danger",
              });
            }
          }}
        >
          Check for update
        </Cell>
        <Cell
          onPress={() => {
            Alert.alert(
              "Do you want to logout?",
              "",
              [
                {
                  text: "Cancel",
                  onPress: () => {},
                  style: "cancel",
                },
                {
                  text: "OK",
                  onPress: () => {
                    setTokens("", "");
                    cache.setQueryData<MeResponse>("/me", { user: null });
                  },
                },
              ],
              { cancelable: false }
            );
          }}
        >
          Logout
        </Cell>
        <Cell
          onPress={() => {
            Alert.alert(
              "Confirm delete account",
              "",
              [
                {
                  text: "Cancel",
                  onPress: () => {},
                  style: "cancel",
                },
                {
                  text: "OK",
                  onPress: async () => {
                    try {
                      await mutate(["/account/delete", {}, "POST"]);
                      setTokens("", "");
                      cache.setQueryData<MeResponse>("/me", { user: null });
                    } catch {}
                  },
                },
              ],
              { cancelable: false }
            );
          }}
        >
          Delete account
        </Cell>
        <MyText
          style={{ textAlign: "center", marginTop: 60, marginBottom: 40 }}
        >
          Version: {pkg.version}
        </MyText>
      </ScrollView>
    </ScreenWrapper>
  );
}
Example #10
Source File: MatchesScreen.tsx    From vsinder-app with Apache License 2.0 4 votes vote down vote up
MatchesScreen: React.FC<MatchesStackNav<"matchy">> = ({
  navigation,
}) => {
  const {
    buttonSecondaryHoverBackground,
    buttonSecondaryBackground,
  } = useTheme();
  const { data, isLoading } = useQuery<MatchesResponse>(
    "/matches/0",
    defaultQueryFn
  );

  const cache = useQueryCache();

  useOnWebSocket((e) => {
    if (e.type === "unmatch") {
      const d = cache.getQueryData<MatchesResponse>("/matches/0");
      if (d) {
        cache.setQueryData<MatchesResponse>("/matches/0", {
          matches: d.matches.filter((m) => m.userId !== e.userId),
        });
      }
    } else if (e.type === "new-match") {
      cache.invalidateQueries("/matches/0");
    } else if (e.type === "new-like") {
      cache.setQueryData<MeResponse>("/me", (u) => {
        return {
          user: {
            ...u!.user!,
            numLikes: u!.user!.numLikes + 1,
          },
        };
      });
    } else if (e.type === "new-message") {
      const state = navigation.dangerouslyGetState();
      const route: any = state.routes[state.index];
      const read = !!(route && route.params?.userId === e.message.senderId);
      const d = cache.getQueryData<MatchesResponse>("/matches/0");
      if (d) {
        cache.setQueryData<MatchesResponse>("/matches/0", {
          matches: d.matches.map((m) =>
            m.userId === e.message.senderId
              ? {
                  ...m,
                  read,
                  message: {
                    text: e.message.text,
                    createdAt: e.message.createdAt,
                  },
                }
              : m
          ),
        });
      }
    }
  });

  useEffect(() => {
    if (data?.matches) {
      const d0 = cache.getQueryData<MeResponse>("/me");
      if (!d0 || !d0.user) {
        return;
      }
      const newUnreadMatchUserIds: Array<{
        userId1: string;
        userId2: string;
      }> = [];
      data.matches.forEach((m) => {
        if (!m.read) {
          const [u1, u2] = [m.userId, d0.user!.id].sort();
          newUnreadMatchUserIds.push({ userId1: u1, userId2: u2 });
        }
      });
      cache.setQueryData<MeResponse>("/me", {
        user: {
          ...d0.user,
          unreadMatchUserIds: newUnreadMatchUserIds,
        },
      });
    }
  }, [data?.matches]);

  if (isLoading) {
    return <FullscreenLoading />;
  }

  if (!data?.matches.length) {
    return <FullscreenMessage message="No matches yet ?" />;
  }

  return (
    <ScreenWrapper noPadding>
      <FlatList
        showsVerticalScrollIndicator={false}
        style={{ paddingTop: 16 }}
        data={data.matches.sort(compareMatches)}
        keyExtractor={(x) => x.userId}
        renderItem={({ item }) => (
          <TouchableOpacity
            onPress={() =>
              navigation.navigate("messages", { ...item, id: item.userId })
            }
            style={{
              flexDirection: "row",
              alignItems: "center",
              backgroundColor: item.read
                ? undefined
                : buttonSecondaryHoverBackground,
              padding: 15,
              borderBottomColor: buttonSecondaryBackground,
              borderBottomWidth: 1,
            }}
          >
            <TouchableOpacity
              style={{ paddingRight: 12 }}
              onPress={() =>
                navigation.navigate(`viewCard`, { id: item.userId })
              }
            >
              <Avatar src={item.photoUrl} />
            </TouchableOpacity>
            <View style={{ flex: 1 }}>
              <View
                style={{
                  alignItems: "center",
                  flexDirection: "row",
                  width: "100%",
                }}
              >
                <MyText
                  style={{ fontSize: 20, marginRight: 4 }}
                  numberOfLines={1}
                >
                  {item.displayName}
                </MyText>
                <Flair name={item.flair as any} />
                <MyText style={{ marginLeft: "auto" }} numberOfLines={1}>
                  {dtToHumanStr(
                    new Date(
                      item.message ? item.message.createdAt : item.createdAt
                    )
                  )}
                </MyText>
              </View>
              <MyText style={{ marginTop: 4 }}>
                {item.message?.text || " "}
              </MyText>
            </View>
          </TouchableOpacity>
        )}
      />
    </ScreenWrapper>
  );
}
Example #11
Source File: EditProfile.tsx    From vsinder-app with Apache License 2.0 4 votes vote down vote up
EditProfile: React.FC<ProfileStackNav<"editProfile">> = ({
  navigation,
}) => {
  const scrollView = useRef<ScrollView>(null);
  const { data, isLoading } = useQuery<MeResponse>("/me");
  const cache = useQueryCache();
  const [mutate, { isLoading: mutIsLoading }] = useMutation(defaultMutationFn);

  if (isLoading) {
    return <FullscreenLoading />;
  }

  return (
    <ScreenWrapper>
      <Formik
        initialValues={
          data?.user ? userToInitialFormData(data.user) : initialProfileData
        }
        validateOnChange={false}
        validateOnBlur={false}
        validationSchema={schema}
        onSubmit={async ({ sendNotifs, ...values }) => {
          const d: any = {
            ...values,
            ageRangeMax: parseInt(values.ageRangeMax),
            ageRangeMin: parseInt(values.ageRangeMin),
            birthday: format(values.birthday, "yyyy-MM-dd"),
          };

          if (data!.user!.hasPushToken !== sendNotifs) {
            if (sendNotifs) {
              const t = await registerForPushNotifications();
              if (t) {
                d.pushToken = t;
              }
            } else {
              d.pushToken = null;
            }
          }
          try {
            const { user } = await mutate(["/user", d, "PUT"]);
            cache.setQueryData("/me", { user });
            cache.invalidateQueries("/feed");
            if (data!.user!.codeImgIds.length) {
              navigation.goBack();
            } else {
              navigation.navigate(`codeSnippeter`, { replace: true });
            }
          } catch {}
        }}
      >
        {({ values, setFieldValue, handleSubmit, errors }) => {
          const age = getAge(values.birthday);
          const tooYoung = age < 18 && Platform.OS === "ios";
          return (
            <KeyboardAwareScrollView
              ref={scrollView}
              showsVerticalScrollIndicator={false}
              keyboardShouldPersistTaps="handled"
            >
              <FormSpacer>
                <TextField name="displayName" label="Name" />
              </FormSpacer>
              <FormSpacer>
                <TextField name="bio" label="Bio" multiline />
              </FormSpacer>
              <FormSpacer>
                <FlairSelectField
                  name="flair"
                  label={
                    values.flair ? (
                      <Flair name={values.flair as any} />
                    ) : (
                      "Flair"
                    )
                  }
                />
              </FormSpacer>
              <FormSpacer>
                <DatePickerField
                  name="birthday"
                  label={`Birthday (used to compute age: ${age})`}
                />
                {tooYoung ? (
                  <ErrorText>
                    You need to be 18 or older to use the app.
                  </ErrorText>
                ) : null}
              </FormSpacer>
              <FormSpacer>
                <RadioGroupField
                  name="goal"
                  label="I'm looking for:"
                  onValue={(v) => {
                    if (v === "friendship") {
                      values.ageRangeMin = initialProfileData.ageRangeMin;
                      values.ageRangeMax = initialProfileData.ageRangeMax;
                    }
                  }}
                  options={[
                    ...(age >= 18
                      ? [
                          {
                            label: "love",
                            value: "love",
                          },
                        ]
                      : []),
                    { label: "friendship", value: "friendship" },
                  ]}
                />
              </FormSpacer>
              {values.goal === "love" ? (
                <>
                  <FormSpacer>
                    <RadioGroupField
                      name="gender"
                      label="Gender:"
                      options={[
                        { label: "male", value: "male" },
                        { label: "female", value: "female" },
                        { label: "non-binary", value: "non-binary" }
                      ]}
                    />
                  </FormSpacer>
                  <FormSpacer>
                    <RadioGroupField
                      name="genderToShow"
                      label="Show me code from:"
                      options={[
                        { label: "males", value: "male" },
                        { label: "females", value: "female" },
                        { label: "everyone", value: "everyone" },
                      ]}
                    />
                  </FormSpacer>
                  <FormSpacer>
                    <View style={{ marginBottom: 8 }}>
                      <Label>Age Range: </Label>
                    </View>
                    <View style={{ flexDirection: "row" }}>
                      <MyTextInput
                        keyboardType="numeric"
                        value={values.ageRangeMin}
                        onChangeText={(x) => setFieldValue("ageRangeMin", x)}
                      />
                      <MyTextInput
                        style={{ marginLeft: 16 }}
                        keyboardType="numeric"
                        value={"" + values.ageRangeMax}
                        onChangeText={(x) => setFieldValue("ageRangeMax", x)}
                      />
                    </View>
                    {errors.ageRangeMax ? (
                      <ErrorText style={{ marginTop: 8 }}>
                        {errors.ageRangeMax}
                      </ErrorText>
                    ) : null}
                    {errors.ageRangeMin ? (
                      <ErrorText style={{ marginTop: 4 }}>
                        {errors.ageRangeMin}
                      </ErrorText>
                    ) : null}
                  </FormSpacer>
                  <FormSpacer>
                    <View style={{ marginBottom: 4 }}>
                      <Label>City: </Label>
                    </View>
                    <LocationAutocomplete
                      defaultValue={values.location}
                      onFocus={() => {
                        scrollView.current?.scrollToEnd();
                      }}
                      onLocation={(x) => {
                        setFieldValue("location", x);
                      }}
                    />
                    <View style={{ marginTop: 6 }}>
                      <CheckboxWithLabel
                        checked={values.global}
                        onPress={() => setFieldValue("global", !values.global)}
                        label="see people from around the world after you've run out of profiles nearby"
                      />
                    </View>
                  </FormSpacer>
                </>
              ) : null}
              <FormSpacer>
                <View style={{ marginBottom: 4 }}>
                  <Label>Notifications: </Label>
                </View>
                <CheckboxWithLabel
                  checked={values.sendNotifs}
                  onPress={() =>
                    setFieldValue("sendNotifs", !values.sendNotifs)
                  }
                  label="send me them on new matches/messages"
                />
              </FormSpacer>
              <View style={{ paddingTop: 10 }}>
                {tooYoung ? null : (
                  <LoadingButton
                    isLoading={mutIsLoading}
                    onPress={() => handleSubmit()}
                  >
                    {data!.user!.codeImgIds.length ? "save" : "next"}
                  </LoadingButton>
                )}
              </View>
              <View style={{ height: 30 }} />
            </KeyboardAwareScrollView>
          );
        }}
      </Formik>
    </ScreenWrapper>
  );
}
Example #12
Source File: ManageCodePics.tsx    From vsinder-app with Apache License 2.0 4 votes vote down vote up
ManageCodePics: React.FC<ProfileStackNav<"manageCodePics">> = ({
  navigation,
}) => {
  const [{ open, start }, setOpen] = useState(closed);
  const { showActionSheetWithOptions } = useActionSheet();
  const { codeImgs, set } = useCodeImgs();
  const cache = useQueryCache();
  const [mutate, { isLoading }] = useMutation(defaultMutationFn);
  const { inputBackground, inputForeground } = useTheme();
  const loadingSomeImgs = codeImgs.some((x) => !x.value);
  return (
    <ScreenWrapper>
      <SelectModal
        isVisible={open}
        onBackButtonPress={() => setOpen(closed)}
        onBackdropPress={() => setOpen(closed)}
        options={codeImgs.map((_, i) => ({ value: i, label: "" + i }))}
        onItem={(item) => {
          const newList = [...codeImgs];
          const target = item.value as number;

          if (start < target) {
            newList.splice(target + 1, 0, newList[start]);
            newList.splice(start, 1);
          } else {
            newList.splice(target, 0, newList[start]);
            newList.splice(start + 1, 1);
          }
          set({ codeImgs: newList });
          setOpen(closed);
        }}
      />
      <ScrollView showsVerticalScrollIndicator={false}>
        {codeImgs.map(({ value, tmpId }, i) =>
          !value ? (
            <View
              key={tmpId}
              style={[
                styles.card,
                {
                  borderRadius: 9,
                  padding: 16,
                  backgroundColor: inputBackground,
                  alignItems: "center",
                  justifyContent: "center",
                  height: 200,
                },
              ]}
            >
              <MyText
                style={{
                  color: inputForeground,
                  fontSize: 18,
                  marginBottom: 20,
                }}
              >
                generating image
              </MyText>
              <Loading />
            </View>
          ) : (
            <TouchableOpacity
              key={tmpId}
              style={styles.card}
              onPress={() => {
                const options = ["Delete", "Move", "Cancel"];
                const destructiveButtonIndex = 0;
                const cancelButtonIndex = 2;
                showActionSheetWithOptions(
                  {
                    options,
                    cancelButtonIndex,
                    destructiveButtonIndex,
                  },
                  (buttonIndex) => {
                    if (buttonIndex === 0) {
                      set({
                        codeImgs: codeImgs.filter((x) => x.tmpId !== tmpId),
                      });
                    } else if (buttonIndex === 1) {
                      setOpen({ open: true, start: i });
                    }
                  }
                );
              }}
            >
              <CodeImage id={value} />
            </TouchableOpacity>
          )
        )}
      </ScrollView>
      <View style={{ height: 20 }} />
      <LoadingButton
        disabled={loadingSomeImgs}
        isLoading={isLoading}
        onPress={async () => {
          try {
            const ids = codeImgs.map((x) => x.value);
            await mutate(["/user/imgs", { codeImgIds: ids }, "PUT"]);
            const d = cache.getQueryData<MeResponse>("/me");
            if (d) {
              cache.setQueryData("/me", {
                user: { ...d.user, codeImgIds: ids },
              });
            }
            if (navigation.canGoBack()) {
              navigation.goBack();
            }
            // navigation.reset({ routes: [{ name: "viewProfile" }] });
          } catch {}
        }}
      >
        {loadingSomeImgs ? " wait for imgs to finish generating" : "save"}
      </LoadingButton>
    </ScreenWrapper>
  );
}
Example #13
Source File: Settings.tsx    From vsinder-app with Apache License 2.0 4 votes vote down vote up
Settings: React.FC<ProfileStackNav<"settings">> = ({
  navigation,
}) => {
  const [status, setStatus] = React.useState<"loading" | "">("");
  const [mutate, { isLoading }] = useMutation(defaultMutationFn);
  const cache = useQueryCache();

  useEffect(() => {
    if (status === "loading") {
      Updates.fetchUpdateAsync()
        .then(() => {
          Updates.reloadAsync();
        })
        .catch((e) => {
          setStatus("");
          showMessage({
            message: (e as Error).message,
            duration: 10000,
            type: "danger",
          });
        });
    }
  }, [status]);

  if (isLoading || status === "loading") {
    return <FullscreenLoading />;
  }

  return (
    <ScreenWrapper noPadding>
      <ScrollView alwaysBounceVertical={false}>
        <Cell onPress={() => navigation.navigate("changeTheme")}>
          change theme
        </Cell>
        <Cell
          onPress={async () => {
            try {
              const update = await Updates.checkForUpdateAsync();
              if (update.isAvailable) {
                Alert.alert(
                  "Update Available",
                  "Would you like to update now?",
                  [
                    {
                      text: "Cancel",
                      style: "cancel",
                    },
                    {
                      text: "OK",
                      onPress: () => {
                        setStatus("loading");
                      },
                    },
                  ],
                  { cancelable: false }
                );
              } else {
                Alert.alert(
                  "You're on the latest version",
                  "",
                  [
                    {
                      text: "OK",
                      onPress: () => {},
                    },
                  ],
                  { cancelable: false }
                );
              }
            } catch (e) {
              showMessage({
                message: (e as Error).message,
                duration: 10000,
                type: "danger",
              });
            }
          }}
        >
          check for update
        </Cell>
        <Cell
          onPress={() => {
            Alert.alert(
              "Do you want to logout?",
              "",
              [
                {
                  text: "Cancel",
                  onPress: () => {},
                  style: "cancel",
                },
                {
                  text: "OK",
                  onPress: () => {
                    setTokens("", "");
                    cache.setQueryData<MeResponse>("/me", { user: null });
                  },
                },
              ],
              { cancelable: false }
            );
          }}
        >
          logout
        </Cell>
        <Cell
          onPress={() => {
            Alert.alert(
              "Confirm delete account",
              "",
              [
                {
                  text: "Cancel",
                  onPress: () => {},
                  style: "cancel",
                },
                {
                  text: "OK",
                  onPress: async () => {
                    try {
                      await mutate(["/account/delete", {}, "POST"]);
                      setTokens("", "");
                      cache.setQueryData<MeResponse>("/me", { user: null });
                    } catch {}
                  },
                },
              ],
              { cancelable: false }
            );
          }}
        >
          delete account
        </Cell>
        <MyText
          style={{ textAlign: "center", marginTop: 60, marginBottom: 40 }}
        >
          Version: {pkg.version}
        </MyText>
      </ScrollView>
    </ScreenWrapper>
  );
}