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
Generated
+14
-4
@@ -50,6 +50,7 @@
|
||||
"@testing-library/react-native": "^12.3.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "18.2",
|
||||
"@types/react-native-confetti": "^0.1.4",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
@@ -62,7 +63,7 @@
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-unused-imports": "^3.2.0",
|
||||
"prettier": "^3.3.2",
|
||||
"typescript": "^5.5.3"
|
||||
"typescript": "~5.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
@@ -8097,6 +8098,15 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-native-confetti": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-native-confetti/-/react-native-confetti-0.1.4.tgz",
|
||||
"integrity": "sha512-StgW1o0dw8ZLCVZ7v6ZVOBu+RYT+PFjtQAhF9k7uLn/avor4UtKhB9cDrF377+JfRw0aHeo3gg9dawZNzM/jOg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
"version": "7.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
|
||||
@@ -19617,9 +19627,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
|
||||
"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
||||
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
|
||||
+2
-1
@@ -54,6 +54,7 @@
|
||||
"@testing-library/react-native": "^12.3.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "18.2",
|
||||
"@types/react-native-confetti": "^0.1.4",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
@@ -66,7 +67,7 @@
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-unused-imports": "^3.2.0",
|
||||
"prettier": "^3.3.2",
|
||||
"typescript": "^5.5.3"
|
||||
"typescript": "~5.3.3"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
+1
-1
@@ -31,7 +31,7 @@ export default function App() {
|
||||
{/* FIXME: Read urlScheme and merchantIdentifier from .Config */}
|
||||
<StripeProvider
|
||||
publishableKey={process.env.EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY || ''}
|
||||
urlScheme={Constants.expoConfig.scheme as string}
|
||||
urlScheme={Constants.expoConfig?.scheme as string}
|
||||
merchantIdentifier="merchant.no.neuf.chateau" // https://developer.apple.com/account/resources/identifiers/list/merchant
|
||||
>
|
||||
<Dusken />
|
||||
|
||||
+11
-22
@@ -1,22 +1,20 @@
|
||||
import { NativeBaseProvider } from 'native-base';
|
||||
import React, { useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Navigation from './Navigation';
|
||||
import { logout as logoutAction, registerUserSuccess as registerUserSuccessAction } from './actions';
|
||||
import { migrateReduxPersistFourToFive } from './utils';
|
||||
import { logout } from './actions';
|
||||
import { useAppDispatch, useAppSelector } from './hooks';
|
||||
|
||||
const Dusken = () => {
|
||||
const user = useAppSelector((store) => store.user);
|
||||
const userToken = useAppSelector((store) => store.userToken);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const DuskenContainer = ({ registerUserSuccess, userToken, user, logout }) => {
|
||||
useEffect(() => {
|
||||
async function inner() {
|
||||
await migrateReduxPersistFourToFive(registerUserSuccess);
|
||||
|
||||
// logout authenticated users without token
|
||||
if (user && !userToken) {
|
||||
logout();
|
||||
}
|
||||
// logout authenticated users without token
|
||||
if (user && !userToken) {
|
||||
dispatch(logout());
|
||||
}
|
||||
inner();
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -26,13 +24,4 @@ const DuskenContainer = ({ registerUserSuccess, userToken, user, logout }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
userToken: state.userToken,
|
||||
user: state.user,
|
||||
});
|
||||
const mapDispatchToProps = {
|
||||
registerUserSuccess: registerUserSuccessAction,
|
||||
logout: logoutAction,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DuskenContainer);
|
||||
export default Dusken;
|
||||
+12
-4
@@ -1,5 +1,5 @@
|
||||
import { SimpleLineIcons as Icon } from '@expo/vector-icons';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import { BottomTabNavigationOptions, createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import React from 'react';
|
||||
@@ -57,10 +57,18 @@ const UserStackScreen = () => (
|
||||
</UserStack.Navigator>
|
||||
);
|
||||
|
||||
type TabBarIconType = BottomTabNavigationOptions['tabBarIcon'];
|
||||
|
||||
const HomeTabs = createBottomTabNavigator<HomeTabParamList>();
|
||||
const EventIcon = ({ color, size }) => <Icon name="event" size={size} color={color} style={{ marginTop: 5 }} />;
|
||||
const UserIcon = ({ color, size }) => <Icon name="user" size={size} color={color} style={{ marginTop: 5 }} />;
|
||||
const AboutIcon = ({ color, size }) => <Icon name="info" size={size} color={color} style={{ marginTop: 5 }} />;
|
||||
const EventIcon: TabBarIconType = ({ color, size }) => (
|
||||
<Icon name="event" size={size} color={color} style={{ marginTop: 5 }} />
|
||||
);
|
||||
const UserIcon: TabBarIconType = ({ color, size }) => (
|
||||
<Icon name="user" size={size} color={color} style={{ marginTop: 5 }} />
|
||||
);
|
||||
const AboutIcon: TabBarIconType = ({ color, size }) => (
|
||||
<Icon name="info" size={size} color={color} style={{ marginTop: 5 }} />
|
||||
);
|
||||
const Navigation = () => (
|
||||
<NavigationContainer>
|
||||
<HomeTabs.Navigator initialRouteName="EventStack">
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ function loginRequest() {
|
||||
};
|
||||
}
|
||||
|
||||
function loginSuccess(username, token) {
|
||||
function loginSuccess(username: string, token: string) {
|
||||
return {
|
||||
type: LOGIN_SUCCESS,
|
||||
username,
|
||||
|
||||
+9
-8
@@ -13,10 +13,11 @@ import {
|
||||
membershipChargeSuccess,
|
||||
} from './actions';
|
||||
import type { UserRegisterFormValues } from './modules/register/useUserRegister';
|
||||
import { AppDispatch } from './reduxStore';
|
||||
import { fetchWithTimeout } from './utils';
|
||||
|
||||
export function requestLogin(username, password) {
|
||||
return (dispatch) => {
|
||||
export function requestLogin(username: string, password: string) {
|
||||
return (dispatch: AppDispatch) => {
|
||||
// We are now logging in
|
||||
dispatch(loginRequest());
|
||||
|
||||
@@ -50,8 +51,8 @@ export function requestLogin(username, password) {
|
||||
};
|
||||
}
|
||||
|
||||
export function requestUserData(authToken) {
|
||||
return (dispatch) => {
|
||||
export function requestUserData(authToken: string) {
|
||||
return (dispatch: AppDispatch) => {
|
||||
// We are now fetching user data
|
||||
dispatch(userDataRequest());
|
||||
|
||||
@@ -82,7 +83,7 @@ export function requestUserData(authToken) {
|
||||
}
|
||||
|
||||
export function requestRegisterUser({ firstName, lastName, email, phoneNumber, password }: UserRegisterFormValues) {
|
||||
return (dispatch) => {
|
||||
return (dispatch: AppDispatch) => {
|
||||
dispatch(registerUserRequest());
|
||||
|
||||
return fetchWithTimeout(`${process.env.EXPO_PUBLIC_DUSKEN_API_URL}/api/user/register/`, {
|
||||
@@ -117,8 +118,8 @@ export function requestRegisterUser({ firstName, lastName, email, phoneNumber, p
|
||||
};
|
||||
}
|
||||
|
||||
export function requestMembershipCharge(authToken, stripeToken, membershipType) {
|
||||
return (dispatch) => {
|
||||
export function requestMembershipCharge(authToken: string, stripeToken: string, membershipType: string) {
|
||||
return (dispatch: AppDispatch) => {
|
||||
dispatch(membershipChargeRequest());
|
||||
|
||||
return fetchWithTimeout(`${process.env.EXPO_PUBLIC_DUSKEN_API_URL}/api/membership/charge/`, {
|
||||
@@ -153,7 +154,7 @@ export function requestMembershipCharge(authToken, stripeToken, membershipType)
|
||||
}
|
||||
|
||||
/** Create a stripe payment sheet. Note that this is not using redux */
|
||||
export async function requestStripePaymentSheet(authToken, membershipType) {
|
||||
export async function requestStripePaymentSheet(authToken: string, membershipType: string) {
|
||||
const body = JSON.stringify({
|
||||
membership_type: membershipType,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
+18
-2
@@ -13,10 +13,26 @@ import {
|
||||
MEMBERSHIP_CHARGE_FAILURE,
|
||||
MEMBERSHIP_CHARGE_SUCCESS,
|
||||
} from './actions';
|
||||
import type { UserType } from './types/data';
|
||||
import { snakeToCamelCase } from './utils';
|
||||
|
||||
// FIXME: type this
|
||||
const initialState = {
|
||||
interface RootState {
|
||||
isAuthenticated: boolean;
|
||||
isLoggingIn: boolean;
|
||||
isFetchingUserData: boolean;
|
||||
isRegisteringUser: boolean;
|
||||
isChargingMembership: boolean;
|
||||
userToken: string | null;
|
||||
user: UserType | null;
|
||||
// FIXME: Type rest
|
||||
loginError: unknown | null;
|
||||
userError: unknown | null;
|
||||
registerError: unknown | null;
|
||||
chargeError: unknown | null;
|
||||
lastOrder: unknown | null;
|
||||
}
|
||||
|
||||
const initialState: RootState = {
|
||||
isAuthenticated: false,
|
||||
isLoggingIn: false,
|
||||
isFetchingUserData: false,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export interface EventType {
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
content: {
|
||||
rendered: string;
|
||||
};
|
||||
@@ -10,4 +11,22 @@ export interface EventType {
|
||||
thumbnail: {
|
||||
medium_large: string;
|
||||
};
|
||||
price_regular: string;
|
||||
price_member: string;
|
||||
ticket_url: string;
|
||||
facebook_url: string;
|
||||
}
|
||||
|
||||
export interface MembershipType {
|
||||
membership_type: string;
|
||||
end_date: string;
|
||||
}
|
||||
|
||||
export interface UserType {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
email: string;
|
||||
last_membership: MembershipType | null;
|
||||
is_volunteer: boolean;
|
||||
is_member: boolean;
|
||||
}
|
||||
+1
-30
@@ -1,6 +1,4 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
export function snakeToCamelCase(val) {
|
||||
export function snakeToCamelCase(val: string) {
|
||||
return val.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
|
||||
}
|
||||
|
||||
@@ -16,30 +14,3 @@ export function fetchWithTimeout(url: string, options?: RequestInit, timeout = 1
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function migrateReduxPersistFourToFive(registerUserSuccess) {
|
||||
const authedKey = 'reduxPersist:isAuthenticated';
|
||||
const userKey = 'reduxPersist:user';
|
||||
const userTokenKey = 'reduxPersist:userToken';
|
||||
const v4Keys = [authedKey, userKey, userTokenKey];
|
||||
|
||||
const asyncStorageKeys = await AsyncStorage.getAllKeys();
|
||||
if (v4Keys.some((key) => !asyncStorageKeys.includes(key))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isAuthenticated = await AsyncStorage.getItem(authedKey);
|
||||
console.log(authedKey, isAuthenticated);
|
||||
if (isAuthenticated !== 'true') {
|
||||
await AsyncStorage.multiRemove(v4Keys);
|
||||
return;
|
||||
}
|
||||
|
||||
const userToken = await AsyncStorage.getItem(userTokenKey);
|
||||
console.log(userTokenKey, userToken);
|
||||
const user = await AsyncStorage.getItem(userKey);
|
||||
console.log(userKey, user, JSON.parse(user));
|
||||
registerUserSuccess({ auth_token: JSON.parse(userToken), ...JSON.parse(user) });
|
||||
await AsyncStorage.multiRemove(v4Keys);
|
||||
console.log('successfully migrated to new redux-persist');
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./"
|
||||
"baseUrl": "./",
|
||||
},
|
||||
"extends": "expo/tsconfig.base"
|
||||
}
|
||||
Reference in new issue
Block a user