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

View File

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

View File

@@ -11,6 +11,7 @@ const constants = {
// Used to construct all API request URLs // Used to construct all API request URLs
API_ENDPOINT, API_ENDPOINT,
SIGN_IN: `${API_ENDPOINT}/api/party-user-auth/sign-in`, SIGN_IN: `${API_ENDPOINT}/api/party-user-auth/sign-in`,
PARTY_USER_JOIN_EVENT: `${API_ENDPOINT}/api/event/partyUserJoinEvent`,
}; };
if (!constants.API_ENDPOINT) { 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 getPartyUserUsername = (state: AppState) => state.user.username;
export const getPartyUserState = (state: AppState) => state.user; 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 // Additional feature reducers
import { orderReducer } from './sessions/orders.reducer'; import { orderReducer } from './sessions/orders.reducer';
import { dummyReducer } from './dummy/dummy.reducer';
export const initialState: AppState = { export const initialState: AppState = {
data: { data: {
@@ -40,10 +41,14 @@ export const initialState: AppState = {
loading: false, loading: false,
// //
isSessionValid: false, isSessionValid: false,
//
}, },
locations: { locations: {
locations: [], locations: [],
}, },
dummy: {
eventIdToJoin: '',
},
}; };
export const reducers = combineReducers({ export const reducers = combineReducers({
@@ -52,6 +57,7 @@ export const reducers = combineReducers({
locations: locationsReducer, locations: locationsReducer,
// //
order: orderReducer, order: orderReducer,
dummy: dummyReducer,
}); });
export type AppState = ReturnType<typeof reducers>; 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, IonContent,
IonPage, IonPage,
IonButtons, IonButtons,
IonMenuButton,
IonButton, IonButton,
IonIcon, IonIcon,
IonDatetime,
IonSelectOption,
IonList,
IonItem,
IonLabel,
IonSelect,
IonPopover, IonPopover,
IonText,
IonFooter, IonFooter,
useIonRouter, useIonRouter,
IonAvatar,
} from '@ionic/react'; } from '@ionic/react';
import './style.scss'; import './style.scss';
import { import {
accessibility, accessibility,
accessibilityOutline,
chevronBackOutline, chevronBackOutline,
ellipsisHorizontal, ellipsisHorizontal,
ellipsisVertical, ellipsisVertical,
heart,
locationOutline,
locationSharp, locationSharp,
logoIonic,
man, man,
manOutline,
people, people,
peopleOutline,
timer,
timerOutline,
timerSharp, timerSharp,
wallet,
walletOutline,
walletSharp, walletSharp,
woman, woman,
womanOutline,
} from 'ionicons/icons'; } from 'ionicons/icons';
import AboutPopover from '../../components/AboutPopover'; import AboutPopover from '../../components/AboutPopover';
import { format, parseISO } from 'date-fns'; import { format, parseISO } from 'date-fns';
import { TestContent } from './TestContent';
import { Helloworld } from '../../api/Helloworld'; import { Helloworld } from '../../api/Helloworld';
import { getEventById } from '../../api/getEventById'; import { getEventById } from '../../api/getEventById';
import { connect } from '../../data/connect'; import { connect } from '../../data/connect';
@@ -62,6 +41,9 @@ import * as selectors from '../../data/selectors';
import { Event } from '../../models/Event'; import { Event } from '../../models/Event';
import { RouteComponentProps } from 'react-router'; import { RouteComponentProps } from 'react-router';
import AvatarRow from './AvatarRow'; 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; const leftShift: number = -25;
@@ -71,7 +53,10 @@ interface OwnProps extends RouteComponentProps {
interface StateProps {} interface StateProps {}
interface DispatchProps {} interface DispatchProps {
setPartyUserMeta: typeof setPartyUserMeta;
setEventIdToJoin: typeof setEventIdToJoin;
}
interface PageProps extends OwnProps, StateProps, DispatchProps {} 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 router = useIonRouter();
const [showPopover, setShowPopover] = useState(false); const [showPopover, setShowPopover] = useState(false);
@@ -136,6 +121,13 @@ const EventDetail: React.FC<PageProps> = ({ event_detail }) => {
router.goBack(); 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</>; if (!event_detail) return <>loading</>;
return ( return (
@@ -268,7 +260,7 @@ const EventDetail: React.FC<PageProps> = ({ event_detail }) => {
margin: '1rem', margin: '1rem',
}} }}
> >
<IonButton expand="full" shape="round"> <IonButton expand="full" shape="round" onClick={handleJoinClick}>
Join Join
</IonButton> </IonButton>
</div> </div>
@@ -286,8 +278,10 @@ const EventDetail: React.FC<PageProps> = ({ event_detail }) => {
}; };
export default connect({ export default connect({
mapDispatchToProps: {
setEventIdToJoin,
},
mapStateToProps: (state, ownProps) => { mapStateToProps: (state, ownProps) => {
console.log({ t1: selectors.getEvents(state) });
return { return {
event_detail: selectors.getEvent(state, ownProps), 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 React, { useEffect, useRef, useState } from 'react';
import './style.scss'; import './style.scss';
const Helloworld: React.FC = () => { const Helloworld: React.FC = ({}) => {
return ( return (
<IonPage id="speaker-list"> <IonPage id="speaker-list">
<IonHeader translucent={true} className="ion-no-border"> <IonHeader translucent={true} className="ion-no-border">