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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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>
);
}