From 7a6014a1157431887e0395d7babc98b9b799385b Mon Sep 17 00:00:00 2001 From: louiscklaw Date: Wed, 18 Jun 2025 04:06:16 +0800 Subject: [PATCH] feat: implement event joining flow with dummy payment page, including route configuration, Redux state management, and UI updates for event detail page --- 03_source/mobile/src/App.tsx | 5 +- 03_source/mobile/src/PATHS.ts | 2 + 03_source/mobile/src/constants.ts | 1 + .../mobile/src/data/dummy/dummy.actions.ts | 14 ++ .../mobile/src/data/dummy/dummy.reducer.ts | 13 ++ .../mobile/src/data/dummy/dummy.state.ts | 3 + 03_source/mobile/src/data/selectors.ts | 2 + 03_source/mobile/src/data/state.ts | 6 + .../src/pages/DummyEventPayPage/index.tsx | 131 ++++++++++++++++++ .../style.scss | 0 .../mobile/src/pages/DummyPayPage/index.tsx | 37 ----- .../mobile/src/pages/EventDetail/index.tsx | 44 +++--- .../mobile/src/pages/Helloworld/index.tsx | 2 +- 13 files changed, 196 insertions(+), 64 deletions(-) create mode 100644 03_source/mobile/src/data/dummy/dummy.actions.ts create mode 100644 03_source/mobile/src/data/dummy/dummy.reducer.ts create mode 100644 03_source/mobile/src/data/dummy/dummy.state.ts create mode 100644 03_source/mobile/src/pages/DummyEventPayPage/index.tsx rename 03_source/mobile/src/pages/{DummyPayPage => DummyEventPayPage}/style.scss (100%) delete mode 100644 03_source/mobile/src/pages/DummyPayPage/index.tsx diff --git a/03_source/mobile/src/App.tsx b/03_source/mobile/src/App.tsx index fee147f..043e2a5 100644 --- a/03_source/mobile/src/App.tsx +++ b/03_source/mobile/src/App.tsx @@ -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 = ({ darkMode, schedule, setIsLoggedIn, + + { diff --git a/03_source/mobile/src/PATHS.ts b/03_source/mobile/src/PATHS.ts index 2712190..7b5f5e7 100644 --- a/03_source/mobile/src/PATHS.ts +++ b/03_source/mobile/src/PATHS.ts @@ -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', diff --git a/03_source/mobile/src/constants.ts b/03_source/mobile/src/constants.ts index 205c9d8..778e2fc 100644 --- a/03_source/mobile/src/constants.ts +++ b/03_source/mobile/src/constants.ts @@ -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) { diff --git a/03_source/mobile/src/data/dummy/dummy.actions.ts b/03_source/mobile/src/data/dummy/dummy.actions.ts new file mode 100644 index 0000000..d78c556 --- /dev/null +++ b/03_source/mobile/src/data/dummy/dummy.actions.ts @@ -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; +// | ActionType diff --git a/03_source/mobile/src/data/dummy/dummy.reducer.ts b/03_source/mobile/src/data/dummy/dummy.reducer.ts new file mode 100644 index 0000000..72299dc --- /dev/null +++ b/03_source/mobile/src/data/dummy/dummy.reducer.ts @@ -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 }; + } +} diff --git a/03_source/mobile/src/data/dummy/dummy.state.ts b/03_source/mobile/src/data/dummy/dummy.state.ts new file mode 100644 index 0000000..e309d88 --- /dev/null +++ b/03_source/mobile/src/data/dummy/dummy.state.ts @@ -0,0 +1,3 @@ +export interface DummyState { + eventIdToJoin?: string; +} diff --git a/03_source/mobile/src/data/selectors.ts b/03_source/mobile/src/data/selectors.ts index 02e6833..13fd2f8 100644 --- a/03_source/mobile/src/data/selectors.ts +++ b/03_source/mobile/src/data/selectors.ts @@ -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; diff --git a/03_source/mobile/src/data/state.ts b/03_source/mobile/src/data/state.ts index e412721..f493820 100644 --- a/03_source/mobile/src/data/state.ts +++ b/03_source/mobile/src/data/state.ts @@ -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; diff --git a/03_source/mobile/src/pages/DummyEventPayPage/index.tsx b/03_source/mobile/src/pages/DummyEventPayPage/index.tsx new file mode 100644 index 0000000..c93d2f2 --- /dev/null +++ b/03_source/mobile/src/pages/DummyEventPayPage/index.tsx @@ -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 = ({ + isLoggedin, + partyUserState, + // + joinEventId, +}) => { + const router = useIonRouter(); + + // if (!isLoggedin) return ; + + 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 ( + + + + + {/* */} + + + + + Dummy pay event page + + + + +
+
This is a dummy page to emulate payment gateway work
+ +
+
pay for event
+
{JSON.stringify(joinEventId)}
+
+ + Pay + Cancel +
+ + setShowJoinOKToast(false)} + /> +
+
+ ); +}; + +export default connect({ + mapStateToProps: (state) => ({ + isLoggedin: state.user.isLoggedin, + // + joinEventId: selectors.getEventIdToJoin(state), + // + partyUserState: selectors.getPartyUserState(state), + }), + component: DummyPayPage, +}); diff --git a/03_source/mobile/src/pages/DummyPayPage/style.scss b/03_source/mobile/src/pages/DummyEventPayPage/style.scss similarity index 100% rename from 03_source/mobile/src/pages/DummyPayPage/style.scss rename to 03_source/mobile/src/pages/DummyEventPayPage/style.scss diff --git a/03_source/mobile/src/pages/DummyPayPage/index.tsx b/03_source/mobile/src/pages/DummyPayPage/index.tsx deleted file mode 100644 index a16c17c..0000000 --- a/03_source/mobile/src/pages/DummyPayPage/index.tsx +++ /dev/null @@ -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 ( - - - - - {/* */} - - - - - Discover Events - - - - DummyPayPage - - ); -}; - -export default DummyPayPage; diff --git a/03_source/mobile/src/pages/EventDetail/index.tsx b/03_source/mobile/src/pages/EventDetail/index.tsx index a56ef27..fb2f2dd 100644 --- a/03_source/mobile/src/pages/EventDetail/index.tsx +++ b/03_source/mobile/src/pages/EventDetail/index.tsx @@ -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[]) => { ); }; -const EventDetail: React.FC = ({ event_detail }) => { +const EventDetail: React.FC = ({ event_detail, setEventIdToJoin }) => { const router = useIonRouter(); const [showPopover, setShowPopover] = useState(false); @@ -136,6 +121,13 @@ const EventDetail: React.FC = ({ 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 = ({ event_detail }) => { margin: '1rem', }} > - + Join @@ -286,8 +278,10 @@ const EventDetail: React.FC = ({ event_detail }) => { }; export default connect({ + mapDispatchToProps: { + setEventIdToJoin, + }, mapStateToProps: (state, ownProps) => { - console.log({ t1: selectors.getEvents(state) }); return { event_detail: selectors.getEvent(state, ownProps), }; diff --git a/03_source/mobile/src/pages/Helloworld/index.tsx b/03_source/mobile/src/pages/Helloworld/index.tsx index 87413ac..105705e 100644 --- a/03_source/mobile/src/pages/Helloworld/index.tsx +++ b/03_source/mobile/src/pages/Helloworld/index.tsx @@ -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 (