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:
18 files changed
+177
-122
No files matched your search
@@ -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}`;
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user