feat: implement event joining flow with dummy payment page, including route configuration, Redux state management, and UI updates for event detail page

This commit is contained in:
louiscklaw
2025-06-18 04:06:16 +08:00
parent 37ace98e60
commit 7a6014a115
13 changed files with 196 additions and 64 deletions

View File

@@ -66,7 +66,8 @@ import PrivacyAgreement from './pages/PrivacyAgreement';
import EventDetail from './pages/EventDetail';
import MemberProfile from './pages/MemberProfile';
import OrderDetail from './pages/OrderDetail';
import DummyPayPage from './pages/DummyPayPage';
import DummyPayPage from './pages/DummyEventPayPage';
import DummyEventPayPage from './pages/DummyEventPayPage';
setupIonicReact();
@@ -142,6 +143,8 @@ const IonicApp: React.FC<IonicAppProps> = ({ darkMode, schedule, setIsLoggedIn,
<Route exact={true} path="/helloworld" component={Helloworld} />
<Route exact={true} path={PATHS.DUMMY_EVENT_PAY_PAGE} component={DummyEventPayPage} />
<Route
path="/logout"
render={() => {

View File

@@ -31,6 +31,8 @@ const PATHS = {
PARTY_USER_SIGN_IN: '/partyUserlogin',
PARTY_USER_SIGN_UP: '/partyUserSignUp',
DUMMY_EVENT_PAY_PAGE: '/DummyEventPayPage',
//
TABS_DEBUG: '/tabs/debug',

View File

@@ -11,6 +11,7 @@ const constants = {
// Used to construct all API request URLs
API_ENDPOINT,
SIGN_IN: `${API_ENDPOINT}/api/party-user-auth/sign-in`,
PARTY_USER_JOIN_EVENT: `${API_ENDPOINT}/api/event/partyUserJoinEvent`,
};
if (!constants.API_ENDPOINT) {

View File

@@ -0,0 +1,14 @@
// import { setIsLoggedInData } from '../dataApi';
import { ActionType } from '../../util/types';
export const setEventIdToJoin = (eventId: string) => {
// await setIsLoggedInData(loggedIn);
return {
type: 'set-dummy-event-id-to-join',
eventId,
} as const;
};
export type DummyActions = ActionType<typeof setEventIdToJoin>;
// | ActionType<typeof checkUserSession>

View File

@@ -0,0 +1,13 @@
import { DummyActions as DummyActions } from './dummy.actions';
import { DummyState as DummyState } from './dummy.state';
export function dummyReducer(state: DummyState, action: DummyActions): DummyState {
switch (action.type) {
case 'set-dummy-event-id-to-join':
console.log('reducer called');
return { ...state, eventIdToJoin: action.eventId };
default:
return { ...state };
}
}

View File

@@ -0,0 +1,3 @@
export interface DummyState {
eventIdToJoin?: string;
}

View File

@@ -211,3 +211,5 @@ export const mapCenter = (state: AppState) => {
export const getPartyUserUsername = (state: AppState) => state.user.username;
export const getPartyUserState = (state: AppState) => state.user;
export const getEventIdToJoin = (state: AppState) => state.dummy.eventIdToJoin;

View File

@@ -15,6 +15,7 @@ import { locationsReducer } from './locations/locations.reducer';
// Additional feature reducers
import { orderReducer } from './sessions/orders.reducer';
import { dummyReducer } from './dummy/dummy.reducer';
export const initialState: AppState = {
data: {
@@ -40,10 +41,14 @@ export const initialState: AppState = {
loading: false,
//
isSessionValid: false,
//
},
locations: {
locations: [],
},
dummy: {
eventIdToJoin: '',
},
};
export const reducers = combineReducers({
@@ -52,6 +57,7 @@ export const reducers = combineReducers({
locations: locationsReducer,
//
order: orderReducer,
dummy: dummyReducer,
});
export type AppState = ReturnType<typeof reducers>;

View File

@@ -0,0 +1,131 @@
// REQ0041/home_discover_event_tab
import {
IonPage,
IonHeader,
IonToolbar,
IonButtons,
IonButton,
IonIcon,
IonTitle,
IonContent,
useIonRouter,
IonToast,
} from '@ionic/react';
import { chevronBackOutline, menuOutline } from 'ionicons/icons';
import React, { useEffect, useRef, useState } from 'react';
import './style.scss';
import PATHS from '../../PATHS';
import axios from 'axios';
import { UserState } from '../../data/user/user.state';
import { connect } from '../../data/connect';
import * as selectors from '../../data/selectors';
import constants from '../../constants';
interface OwnProps {}
interface StateProps {
isLoggedin: boolean;
//
partyUserState: UserState;
//
joinEventId: string;
}
interface DispatchProps {}
interface PageProps extends OwnProps, StateProps, DispatchProps {}
const DummyPayPage: React.FC<PageProps> = ({
isLoggedin,
partyUserState,
//
joinEventId,
}) => {
const router = useIonRouter();
// if (!isLoggedin) return <NotLoggedIn />;
async function handlePayClick() {
try {
await axios.post(constants.PARTY_USER_JOIN_EVENT, {
data: {
eventItemId: joinEventId,
email: partyUserState.meta?.email,
},
});
router.goBack();
setShowJoinOKToast(true);
} catch (error) {
console.error(error);
}
}
function handleCancelClick() {
router.goBack();
}
const [showJoinOKToast, setShowJoinOKToast] = useState(false);
return (
<IonPage id="speaker-list">
<IonHeader translucent={true} className="ion-no-border">
<IonToolbar>
<IonButtons slot="start">
{/* <IonMenuButton /> */}
<IonButton shape="round">
<IonIcon slot="icon-only" icon={chevronBackOutline}></IonIcon>
</IonButton>
</IonButtons>
<IonTitle>Dummy pay event page</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent fullscreen={true}>
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
gap: '1rem',
textAlign: 'center',
padding: '3rem',
}}
>
<div>This is a dummy page to emulate payment gateway work</div>
<div>
<div>pay for event</div>
<pre style={{ backgroundColor: 'RGB(0,0,0, 0.1)' }}>{JSON.stringify(joinEventId)}</pre>
</div>
<IonButton onClick={handlePayClick}>Pay</IonButton>
<IonButton onClick={handleCancelClick}>Cancel</IonButton>
</div>
<IonToast
isOpen={showJoinOKToast}
message="ok, event paid, thank you..."
duration={2000}
// onDidDismiss={() => setShowJoinOKToast(false)}
/>
</IonContent>
</IonPage>
);
};
export default connect<OwnProps, StateProps, DispatchProps>({
mapStateToProps: (state) => ({
isLoggedin: state.user.isLoggedin,
//
joinEventId: selectors.getEventIdToJoin(state),
//
partyUserState: selectors.getPartyUserState(state),
}),
component: DummyPayPage,
});

View File

@@ -1,37 +0,0 @@
// REQ0041/home_discover_event_tab
import {
IonPage,
IonHeader,
IonToolbar,
IonButtons,
IonButton,
IonIcon,
IonTitle,
IonContent,
} from '@ionic/react';
import { menuOutline } from 'ionicons/icons';
import React, { useEffect, useRef, useState } from 'react';
import './style.scss';
const DummyPayPage: React.FC = () => {
return (
<IonPage id="speaker-list">
<IonHeader translucent={true} className="ion-no-border">
<IonToolbar>
<IonButtons slot="end">
{/* <IonMenuButton /> */}
<IonButton shape="round" id="events-open-modal" expand="block">
<IonIcon slot="icon-only" icon={menuOutline}></IonIcon>
</IonButton>
</IonButtons>
<IonTitle>Discover Events</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent fullscreen={true}>DummyPayPage</IonContent>
</IonPage>
);
};
export default DummyPayPage;

View File

@@ -13,48 +13,27 @@ import {
IonContent,
IonPage,
IonButtons,
IonMenuButton,
IonButton,
IonIcon,
IonDatetime,
IonSelectOption,
IonList,
IonItem,
IonLabel,
IonSelect,
IonPopover,
IonText,
IonFooter,
useIonRouter,
IonAvatar,
} from '@ionic/react';
import './style.scss';
import {
accessibility,
accessibilityOutline,
chevronBackOutline,
ellipsisHorizontal,
ellipsisVertical,
heart,
locationOutline,
locationSharp,
logoIonic,
man,
manOutline,
people,
peopleOutline,
timer,
timerOutline,
timerSharp,
wallet,
walletOutline,
walletSharp,
woman,
womanOutline,
} from 'ionicons/icons';
import AboutPopover from '../../components/AboutPopover';
import { format, parseISO } from 'date-fns';
import { TestContent } from './TestContent';
import { Helloworld } from '../../api/Helloworld';
import { getEventById } from '../../api/getEventById';
import { connect } from '../../data/connect';
@@ -62,6 +41,9 @@ import * as selectors from '../../data/selectors';
import { Event } from '../../models/Event';
import { RouteComponentProps } from 'react-router';
import AvatarRow from './AvatarRow';
import { setPartyUserMeta } from '../../data/user/user.actions';
import { setEventIdToJoin } from '../../data/dummy/dummy.actions';
import PATHS from '../../PATHS';
const leftShift: number = -25;
@@ -71,7 +53,10 @@ interface OwnProps extends RouteComponentProps {
interface StateProps {}
interface DispatchProps {}
interface DispatchProps {
setPartyUserMeta: typeof setPartyUserMeta;
setEventIdToJoin: typeof setEventIdToJoin;
}
interface PageProps extends OwnProps, StateProps, DispatchProps {}
@@ -90,7 +75,7 @@ const showJoinedMembers = (joinMembers: Record<string, any>[]) => {
);
};
const EventDetail: React.FC<PageProps> = ({ event_detail }) => {
const EventDetail: React.FC<PageProps> = ({ event_detail, setEventIdToJoin }) => {
const router = useIonRouter();
const [showPopover, setShowPopover] = useState(false);
@@ -136,6 +121,13 @@ const EventDetail: React.FC<PageProps> = ({ event_detail }) => {
router.goBack();
}
function handleJoinClick() {
if (event_detail && event_detail?.id) {
setEventIdToJoin(event_detail.id);
router.push(PATHS.DUMMY_EVENT_PAY_PAGE);
}
}
if (!event_detail) return <>loading</>;
return (
@@ -268,7 +260,7 @@ const EventDetail: React.FC<PageProps> = ({ event_detail }) => {
margin: '1rem',
}}
>
<IonButton expand="full" shape="round">
<IonButton expand="full" shape="round" onClick={handleJoinClick}>
Join
</IonButton>
</div>
@@ -286,8 +278,10 @@ const EventDetail: React.FC<PageProps> = ({ event_detail }) => {
};
export default connect({
mapDispatchToProps: {
setEventIdToJoin,
},
mapStateToProps: (state, ownProps) => {
console.log({ t1: selectors.getEvents(state) });
return {
event_detail: selectors.getEvent(state, ownProps),
};

View File

@@ -14,7 +14,7 @@ import { menuOutline } from 'ionicons/icons';
import React, { useEffect, useRef, useState } from 'react';
import './style.scss';
const Helloworld: React.FC = () => {
const Helloworld: React.FC = ({}) => {
return (
<IonPage id="speaker-list">
<IonHeader translucent={true} className="ion-no-border">