Misc typing improvements++

Turned on compilerOptions.strict in tsconfig.json
and and did a bunch of ad hoc changes. Typed most of the implicit anys

 - Remove redux 4 to 5 migration
 - Stop prop drilling user state
 - Add more data types
 - Start typing redux state
 - Started using redux hooks in Dusken.tsx in favor of the connect HOC
This commit is contained in:
nikolark committed 2024-07-07 21:52:48 +02:00
1 parent ce43893f9d
commit 7a1fb19f56
18 files changed
+177 -122

No files matched your search

+12 -10
View File
@@ -15,6 +15,7 @@ import {
import RenderHtml from 'react-native-render-html';
import theme from 'src/theme';
import type { EventType } from 'src/types/data';
import type { EventDetailProps } from 'src/types/navigation';
moment.locale('nb');
@@ -27,11 +28,11 @@ const FORMAT_DAY = 'DD.';
const FORMAT_LOCAL_DAY = 'dddd';
const FORMAT_YEAR = 'YYYY';
const formatRelative = (time) => moment(time).fromNow();
const formatRelative = (time: string) => moment(time).fromNow();
const format = (time, formatString = FORMAT_TIME) => moment(time).format(formatString);
const format = (time: string, formatString = FORMAT_TIME) => moment(time).format(formatString);
const formatTicketText = (event) => {
const formatTicketText = (event: EventType) => {
/* FIXME: This should be handled by API */
let regularPrice = '';
let memberPrice = '';
@@ -61,7 +62,7 @@ const formatTicketText = (event) => {
return text;
};
const onFacebookUrlPress = (link) => {
const onFacebookUrlPress = (link: string) => {
const fbEventPattern = /https?:\/\/www\.facebook\.com\/events\/(\d+)\/?/i;
const matches = fbEventPattern.exec(link);
let iosURL = link;
@@ -82,15 +83,16 @@ const onFacebookUrlPress = (link) => {
.catch((err) => console.error('An error occurred', err));
};
const onLocationPress = (venue) => {
const onLocationPress = (venue: string) => {
if (venue !== 'Annetsteds') {
Linking.openURL(`https://www.google.com/maps/dir/?api=1&destination=${CHATEAU_NEUF_ADDRESS}`);
}
};
const VenueLocation = ({ venue }) => (venue !== 'Annetsteds' ? CHATEAU_NEUF_ADDRESS : 'Ukjent adresse');
const VenueLocation = ({ venue }: Pick<EventType, 'venue'>) =>
venue !== 'Annetsteds' ? CHATEAU_NEUF_ADDRESS : 'Ukjent adresse';
const PriceAndTicket = ({ event }) => {
const PriceAndTicket = ({ event }: { event: EventType }) => {
const text = formatTicketText(event);
if (text === '') {
@@ -124,7 +126,7 @@ const PriceAndTicket = ({ event }) => {
);
};
const FaceButton = ({ event }) => {
const FaceButton = ({ event }: { event: EventType }) => {
if (!event.facebook_url) {
return null;
}
@@ -141,14 +143,14 @@ const FaceButton = ({ event }) => {
);
};
const Year = ({ startTime }) => {
const Year = ({ startTime }: { startTime: string }) => {
if (moment().year() === moment(startTime).year()) {
return null;
}
return <Text style={styles.year}>{format(startTime, FORMAT_YEAR)}</Text>;
};
const formatTimeFull = (event) => {
const formatTimeFull = (event: EventType) => {
const endTime = event.end_time ? ` - ${format(event.end_time, FORMAT_TIME)}` : '';
const startTime = `${format(event.start_time, FORMAT_LOCAL_DAY)} kl. ${format(event.start_time, FORMAT_TIME)}`;
return `${startTime}${endTime}`;
+1 -1
View File
@@ -41,7 +41,7 @@ const Login = () => {
small
light
onPress={() => {
Linking.openURL(process.env.EXPO_PUBLIC_DUSKEN_FORGOT_PASSWORD_URL);
Linking.openURL(process.env.EXPO_PUBLIC_DUSKEN_FORGOT_PASSWORD_URL || '');
}}
style={styles.forgotPasswordButton}
>
+2 -4
View File
@@ -8,7 +8,7 @@ import Proof from './Proof';
import useMembership from './useMembership';
const Membership = () => {
const { isFetchingUserData, onRegisterPress, onLoginPress, onLogoutPress, user, isAuthenticated, fetchUser } =
const { isFetchingUserData, onRegisterPress, onLoginPress, onLogoutPress, isAuthenticated, fetchUser } =
useMembership();
if (isFetchingUserData) {
@@ -20,9 +20,7 @@ const Membership = () => {
}
if (isAuthenticated) {
return (
<Proof onLogoutPress={onLogoutPress} user={user} fetchUser={fetchUser} isFetchingUserData={isFetchingUserData} />
);
return <Proof onLogoutPress={onLogoutPress} fetchUser={fetchUser} isFetchingUserData={isFetchingUserData} />;
}
return <MembershipInfo onRegister={onRegisterPress} onLogin={onLoginPress} />;
+5 -1
View File
@@ -4,7 +4,11 @@ import { Button } from 'react-native-ui-lib';
import theme from 'src/theme';
const MembershipInfo = ({ onRegister, onLogin }) => (
interface MembershipInfoProps {
onRegister: () => void;
onLogin: () => void;
}
const MembershipInfo = ({ onRegister, onLogin }: MembershipInfoProps) => (
<ScrollView>
<View style={styles.card}>
<Text style={styles.headerTitle}>Hei 👋</Text>
+48 -13
View File
@@ -9,18 +9,20 @@ import {
RefreshControl,
ActivityIndicator,
Dimensions,
GestureResponderEvent,
} from 'react-native';
import Confetti from 'react-native-confetti';
import { Button, Incubator } from 'react-native-ui-lib';
import theme from 'src/theme';
import type { UserType } from 'src/types/data';
import useProof from './useProof';
import logo from '../../../assets/DNS-logo-svart.png';
const { Toast } = Incubator;
const MembershipValidTo = ({ user }) => {
const MembershipValidTo = ({ user }: { user: UserType }) => {
if (!user.last_membership) {
return null;
}
@@ -38,7 +40,13 @@ const MembershipValidTo = ({ user }) => {
);
};
const MembershipStatus = ({ user, onMembershipPress }) => {
const MembershipStatus = ({
user,
onMembershipPress,
}: {
user: UserType;
onMembershipPress: (event: GestureResponderEvent) => void | undefined;
}) => {
if (!user.is_member) {
if (!user.last_membership) {
return null;
@@ -62,7 +70,21 @@ const MembershipStatus = ({ user, onMembershipPress }) => {
);
};
const PurchaseButton = ({ user, paymentLoading, isLoadingMembershipType, onChargePress, membershipPrice }) => {
interface PurchaseButtonProps {
user: UserType;
paymentLoading: boolean;
isLoadingMembershipType: boolean;
membershipPrice: number;
onChargePress: () => void;
}
const PurchaseButton = ({
user,
paymentLoading,
isLoadingMembershipType,
onChargePress,
membershipPrice,
}: PurchaseButtonProps) => {
if (user.is_member) {
return null;
}
@@ -85,7 +107,8 @@ const PurchaseButton = ({ user, paymentLoading, isLoadingMembershipType, onCharg
);
};
const UserConfetti = ({ user, confettiRef }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const UserConfetti = ({ user, confettiRef }: { user: UserType; confettiRef: any }) => {
if (!user.is_member) {
return null;
}
@@ -93,7 +116,7 @@ const UserConfetti = ({ user, confettiRef }) => {
// Confetti!!!!
return <Confetti ref={confettiRef} confettiCount={Number.MAX_SAFE_INTEGER} />;
};
const Logo = ({ user }) => {
const Logo = ({ user }: { user: UserType }) => {
if (!user.is_member) {
return null;
}
@@ -117,7 +140,7 @@ const ChargeError = ({ chargeError }) => {
);
};
const MembershipName = ({ user }) => {
const MembershipName = ({ user }: { user: UserType }) => {
let name = `${user.first_name} ${user.last_name}`;
if (name === ' ') {
name = user.email;
@@ -129,14 +152,26 @@ const MembershipName = ({ user }) => {
);
};
const Proof = ({ onLogoutPress, user, fetchUser, isFetchingUserData }) => {
interface ProofProps {
onLogoutPress: () => void;
fetchUser: () => void;
isFetchingUserData: boolean;
}
const Proof = ({ onLogoutPress, fetchUser, isFetchingUserData }: ProofProps) => {
const CONFETTI_TIMEOUT = 15000;
const confettiRef = useRef(null);
const { chargeError, paymentLoading, isLoadingMembershipType, onChargePress, membershipPrice, toast, setToast } =
useProof({
user,
fetchUser,
});
const confettiRef = useRef<Confetti>(null);
const {
chargeError,
paymentLoading,
isLoadingMembershipType,
onChargePress,
membershipPrice,
toast,
setToast,
user,
} = useProof({
fetchUser,
});
useEffect(() => {
const currentRef = confettiRef.current;
-2
View File
@@ -11,7 +11,6 @@ const useMembership = () => {
const isAuthenticated = useAppSelector((store) => store.isAuthenticated);
const isFetchingUserData = useAppSelector((store) => store.isFetchingUserData);
const lastOrder = useAppSelector((store) => store.lastOrder);
const user = useAppSelector((store) => store.user);
const userToken = useAppSelector((store) => store.userToken);
const prevUserToken = usePrevious(userToken);
const prevLastOrder = usePrevious(lastOrder);
@@ -39,7 +38,6 @@ const useMembership = () => {
isAuthenticated,
isFetchingUserData,
userToken,
user,
lastOrder,
onLogoutPress,
onRegisterPress,
+20 -17
View File
@@ -2,10 +2,9 @@ import { gql, useQuery } from '@apollo/client';
import { useStripe } from '@stripe/stripe-react-native';
import Constants from 'expo-constants';
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { requestStripePaymentSheet } from 'src/api';
import { RootState } from 'src/reduxStore';
import { useAppSelector } from 'src/hooks';
const PROOF_QUERY = gql`
{
@@ -16,22 +15,21 @@ const PROOF_QUERY = gql`
}
`;
function getPrice(membershipTypes) {
if (!membershipTypes) {
return 0;
}
return membershipTypes[0].price / 100;
interface ToastProps {
message: string;
visible: boolean;
preset: 'failure' | 'success';
}
const useProof = ({ user, fetchUser }) => {
const userToken = useSelector((state: RootState) => state.userToken);
const chargeError = useSelector((state: RootState) => state.chargeError);
const useProof = ({ fetchUser }) => {
const user = useAppSelector((state) => state.user);
const userToken = useAppSelector((state) => state.userToken);
const chargeError = useAppSelector((state) => state.chargeError);
const { data, loading } = useQuery(PROOF_QUERY);
const { initPaymentSheet, presentPaymentSheet } = useStripe();
const [paymentLoading, setPaymentLoading] = useState(false);
const membershipTypeSlug = data?.membershipTypes?.[0]?.slug ?? 'standard';
const [toast, setToast] = useState(null);
const [toast, setToast] = useState<ToastProps | null>(null);
const openPaymentSheet = async () => {
try {
@@ -48,7 +46,10 @@ const useProof = ({ user, fetchUser }) => {
const initializePaymentSheet = async () => {
setPaymentLoading(true);
const { paymentIntent, ephemeralKey, customer } = await requestStripePaymentSheet(userToken, membershipTypeSlug);
const { paymentIntent, ephemeralKey, customer } = await requestStripePaymentSheet(
userToken as string,
membershipTypeSlug,
);
const { error } = await initPaymentSheet({
merchantDisplayName: 'Det Norske Studentersamfund',
@@ -56,13 +57,13 @@ const useProof = ({ user, fetchUser }) => {
customerEphemeralKeySecret: ephemeralKey,
paymentIntentClientSecret: paymentIntent,
defaultBillingDetails: {
email: user.email,
name: `${user.first_name} ${user.last_name}`,
email: user?.email,
name: `${user?.first_name} ${user?.last_name}`,
address: {
country: 'NO',
},
},
returnURL: `${Constants.expoConfig.scheme}://stripe-redirect`,
returnURL: `${Constants.expoConfig?.scheme}://stripe-redirect`,
});
if (!error) {
setPaymentLoading(true);
@@ -78,14 +79,16 @@ const useProof = ({ user, fetchUser }) => {
}
}, [fetchUser, user?.last_membership]);
const membershipPrice = data?.membershipTypes?.[0]?.price;
return {
chargeError: chargeError || '',
isLoadingMembershipType: loading,
membershipPrice: getPrice(data?.membershipTypes),
membershipPrice: membershipPrice ? membershipPrice / 100 : 0,
onChargePress: openPaymentSheet,
paymentLoading,
toast,
setToast,
user,
};
};