LifeSports Application(ReactNative & Nest.js) - 19. 마이페이지 대관 내역
#1 UI
UI를 수정해보도록 하겠습니다.
- ./src/pages/user/components/MyRentalFragment.js
import React, { useEffect } from 'react';
import { StyleSheet, View } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { getRentals } from '../../../modules/rentals';
import Loading from '../../../styles/common/Loading';
import palette from '../../../styles/palette';
import MyRenalCard from './MyRentalCard';
const MyRentalFramgment = () => {
const dispatch = useDispatch();
const {
userId,
rentals
} = useSelector(({
user,
rentals
}) => ({
userId: user.user.userId,
rentals: rentals.rentals
}));
useEffect(() => {
dispatch(getRentals(userId));
}, [dispatch]);
return(
<View style={ styles.container }>
{
rentals ?
rentals.map((item, i) => {
return <MyRenalCard i={ i }
item={ item }
/>
}) : <Loading />
}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: palette.gray[2],
}
});
export default MyRentalFramgment;
- ./src/pages/user/components/MyRentalCard.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import PaymentButton from '../components/PaymentButton';
import palette from '../../../styles/palette';
const MyRenalCard = ({ item }) => {
return(
<View style={ styles.container }>
<Text style={ styles.font }>
{ item.date + " " + item.time }
</Text>
<Text style={ styles.font }>
{ item.mapName }
</Text>
{
!item.payment &&
<PaymentButton rentalId={ item.rentalId }/>
}
</View>
);
};
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
width: 350,
marginLeft: 30,
marginTop: 15,
marginBottom: 5,
paddingTop: 10,
paddingBottom: 10,
backgroundColor: palette.white[0]
},
font: {
fontWeight: 'bold',
fontSize: 15
}
});
export default MyRenalCard;
- ./src/pages/user/components/PaymentButton.js
import React from 'react';
import { useNavigation } from '@react-navigation/native';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import palette from '../../../styles/palette';
import { useDispatch } from 'react-redux';
import { deleteRental } from '../../../modules/rental';
const PaymentButton = rentalId => {
const dispatch = useDispatch();
const navigation = useNavigation();
const toPaymentPage = e => {
navigation.navigate("Payment", {
rentalId: rentalId.rentalId
});
};
const onCancel = e => {
dispatch(deleteRental(rentalId.rentalId));
};
return (
<View style={ styles.container }>
<TouchableOpacity style={ styles.payment_shape }
onPress={ toPaymentPage }
>
<Text style={ styles.font }>
결제하기
</Text>
</TouchableOpacity>
<TouchableOpacity style={ styles.cancel_shape }
onPress={ onCancel }
>
<Text style={ styles.font }>
취소하기
</Text>
</TouchableOpacity>
</View>
)
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
},
payment_shape: {
width: '30%',
height: 60,
borderRadius: 6,
margin: 10,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: palette.blue[2],
},
cancel_shape: {
width: '30%',
height: 60,
borderRadius: 6,
margin: 10,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: palette.red[0],
},
font: {
fontSize: 18,
color: palette.white[0]
}
});
export default PaymentButton;
다음과 같이 StackNavigation에서 PaymentScreen을 등록하여 결제하기 버튼을 누르면 결제창으로 넘어갈 수 있도록 수정하도록 하겠습니다.
- ./src/navigator/user/MyPageStackNavigation.js
...
const Stack = createStackNavigator();
const MyPageStackNavigation = () => {
return(
<Stack.Navigator>
...
<Stack.Screen name="Payment"
component={ PaymentScreen }
/>
</Stack.Navigator>
);
};
export default MyPageStackNavigation;
여기까지 결제내역 스크린 테스트를 진행해보도록 하겠습니다.
대여 내역들이 잘 출력되는 모습을 볼 수 있습니다. 그러면 이어서 결제하기의 클릭이벤트, 취소하기의 클릭이벤트를 위주로 UI를 작성해보도록 하겠습니다.
우선 결제하기입니다. 앞서 작성한 PaymentButton에서 toPaymentPage 이벤트를 보면 rentalId를 navigation에 담아 Payment페이지로 이동할 수 있도록 작성하였습니다. 그러면 PaymentScreen에서 이 rentalId를 받아 대관 상세내역을 볼 수 있겠죠.
- ./src/pages/payment/components/PaymentFragment.js
import React, { useEffect } from 'react';
import { StyleSheet, View } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import palette from '../../../styles/palette';
import Loading from '../../../styles/common/Loading';
import PaymentContent from './PaymentContent';
import { useRoute } from '@react-navigation/native';
import { getRental } from '../../../modules/rentals';
const PaymentFragment = () => {
const dispatch = useDispatch();
const route = useRoute();
const { rental } = useSelector(({ rentals }) => ({ rental: rentals.rental }));
useEffect(() => {
dispatch(getRental(route.params.rentalId));
}, [dispatch, route]);
return(
<View style={ styles.container }>
{
rental ?
<PaymentContent rental={ rental }/> :
<Loading />
}
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'column',
width: '90%',
height: '90%',
backgroundColor: palette.white[0],
},
});
export default PaymentFragment;
rentalId를 기반으로 호출한 대관 데이터를 PaymentContent 컴포넌트로 보내도록 하겠습니다.
- ./src/pages/payment/components/PaymentContent.js
import React, { useEffect } from 'react';
import { useNavigation } from '@react-navigation/native';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import palette from '../../../styles/palette';
import StyledTextInput from '../../../styles/common/StyledTextInput';
import { initialize } from '../../../modules/rentals';
import { changeField, requestPayment } from '../../../modules/payment';
const PaymentContent = rental => {
const {
paymentName,
payer,
rentalId,
price,
payment,
paymentError,
} = useSelector(({
payment,
}) => ({
paymentName: payment.paymentName,
payer: payment.payer,
rentalId: payment.rentalId,
price: payment.price,
payment: payment.payment,
paymentError: payment.paymentError,
}));
const dispatch = useDispatch();
const navigation = useNavigation();
const onPayment = () => {
dispatch(requestPayment({
paymentName,
payer,
rentalId,
price
}));
dispatch(initialize());
};
useEffect(() => {
dispatch(changeField({
key: 'paymentName',
value: rental.rental.mapName
}))
}, []);
useEffect(() => {
dispatch(changeField({
key: 'payer',
value: rental.rental.borrower
}))
}, []);
useEffect(() => {
dispatch(changeField({
key: 'rentalId',
value: rental.rental.rentalId
}))
}, []);
useEffect(() => {
dispatch(changeField({
key: 'price',
value: rental.rental.price
}))
}, []);
useEffect(() => {
if(payment) {
dispatch(initialize())
navigation.navigate("MyPage");
}
if(paymentError) {
// setError
}
}, [dispatch, payment, paymentError]);
return(
<View>
<View style={ styles.row } >
<Text style={ styles.label } >
대관 번호
</Text>
<StyledTextInput placeholderTextColor={ palette.black[0] }
value={ rental.rental.rentalId }
/>
</View>
<View style={ styles.row }>
<Text style={ styles.label }>
결제 내역
</Text>
<StyledTextInput placeholderTextColor={ palette.gray[3] }
value={ rental.rental.mapName }
/>
</View>
<View style={ styles.row }>
<Text style={ styles.label }>
결제 금액
</Text>
<StyledTextInput placeholderTextColor={ palette.gray[3] }
value={ rental.rental.price.toString() }
/>
</View>
<View style={ styles.row }>
<Text style={ styles.label } >
사용자명
</Text>
<StyledTextInput placeholderTextColor={ palette.gray[3] }
value={ rental.rental.borrower }
/>
</View>
<View style={ styles.row } >
<Text style={ styles.label }>
체육관 전화번호
</Text>
<StyledTextInput placeholderTextColor={ palette.gray[3] }
value={ rental.rental.tel }
/>
</View>
<View style={ styles.row } >
<Text style={ styles.label } >
대관 날짜
</Text>
<StyledTextInput placeholderTextColor={ palette.gray[3] }
value={ rental.rental.date + "\t" + rental.rental.time }
/>
</View>
<View style={ styles.button_container }>
<TouchableOpacity style={ styles.shape }
onPress={ onPayment }
>
<Text style={ styles.font }>
결제하기
</Text>
</TouchableOpacity>
</View>
</View>
);
};
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
alignItems: 'center',
overflow: 'hidden'
},
label: {
fontWeight: 'bold',
padding: 10,
},
button_container: {
justifyContent: 'center',
alignItems: 'center',
marginTop: 30
},
shape: {
justifyContent: 'center',
alignItems: 'center',
width: '80%',
height: 70,
backgroundColor: palette.blue[2]
},
font: {
justifyContent: 'center',
alignItems: 'center',
fontWeight: 'bold',
fontSize: 20,
color: palette.white[0]
}
});
export default PaymentContent;
결제 상세내역 UI를 확인해보겠습니다.
상세내역 UI도 잘 출력이 되는 모습을 볼 수 있습니다.
그러면 취소하기 관련 이벤트도 작성해보도록 하겠습니다.
#2 취소하기
- ./src/lib/api/rental.js
import client from './client';
...
export const deleteRental = rentalId => client.delete(`http://10.0.2.2:8000/rental-service/${rentalId}/rental`);
rental.js에 deleteRental 액션을 추가하도록 하겠습니다. 이 액션 함수를 이용하여 취소하기 버튼을 누르면 대관 데이터 삭제가 이루어집니다.
- ./src/modules/rental.js
...
const [
DELETE_RENTAL,
DELETE_RENTAL_SUCCESS,
DELETE_RENTAL_FAILURE
] = createRequestActionTypes('rental/DELETE_RENTAL');
...
export const deleteRental = createAction(DELETE_RENTAL, rentalId => rentalId);
const makeRentalSaga = createRequestSaga(MAKE_RENTAL, rentalAPI.rental);
const deleteRentalSaga = createRequestSaga(DELETE_RENTAL, rentalAPI.deleteRental);
export function* rentalSaga() {
yield takeLatest(MAKE_RENTAL, makeRentalSaga);
yield takeLatest(DELETE_RENTAL, deleteRentalSaga);
}
const initialState = {
price: null,
borrower: '',
tel: '',
userId: '',
date: null,
time: null,
mapId: '',
mapName: '',
rental: null,
rentalError: null,
message: null,
};
const rental = handleActions(
{
...
[DELETE_RENTAL_SUCCESS]: (state, { payload: message }) => ({
...state,
message,
}),
[DELETE_RENTAL_FAILURE]: (state, { payload: rentalError }) => ({
...state,
rentalError,
}),
},
initialState,
);
export default rental;
rental-service의 컨트롤러에 endpoint를 추가하겠습니다.
- ./src/app.controller.ts
...
@Controller('rental-service')
export class AppController {
...
@Delete(':rentalId/rental')
public async deleteRental(@Param('rentalId') rentalId: string): Promise<any> {
try {
const result: any = this.rentalService.deleteRental(rentalId);
if(result.status === statusConstants.ERROR) {
return await Object.assign({
status: HttpStatus.INTERNAL_SERVER_ERROR,
payload: null,
message: "Error message: " + result.message
});
}
return await Object.assign({
status: HttpStatus.OK,
payload: null,
message: "Successful delete one"
});
} catch(err) {
return await Object.assign({
status: HttpStatus.BAD_REQUEST,
payload: null,
message: "Error message: " + err
});
}
}
@EventPattern('PAYMENT_RESPONSE')
public async responsePayment(data: any): Promise<any> {
try {
if(data === 'FAILURE_PAYMENT') {
const result: any = await this.rentalService.deleteRental(data.rentalId);
if(result.status === statusConstants.ERROR) {
return await Object.assign({
status: HttpStatus.INTERNAL_SERVER_ERROR,
payload: null,
message: "Error message: " + result.message
});
}
return await Object.assign({
status: HttpStatus.INTERNAL_SERVER_ERROR,
payload: null,
message: "Error message: " + data
});
}
const result: any = await this.rentalService.completeRental(Builder(RentalDto).rentalId(data.rentalId)
.build());
if(result.status === statusConstants.ERROR) {
return await Object.assign({
status: HttpStatus.INTERNAL_SERVER_ERROR,
payload: null,
message: "Error message: " + result.message
});
}
return await Object.assign({
status: HttpStatus.OK,
payload: null,
message: "Successful complete rental!"
});
} catch(err) {
return await Object.assign({
status: HttpStatus.INTERNAL_SERVER_ERROR,
payload: null,
message: "Error message: " + err
});
}
}
}
이전에 RentalService에서 만들었던 deleteOne 메서드의 인자를 string형으로 고치도록 하겠습니다.
- ./src/rental/rental.service.ts
...
@Injectable()
export class RentalService {
...
public async deleteRental(rentalId: string): Promise<any> {
try {
const result = await this.rentalModel.deleteOne({ rentalId: rentalId });
if(!result) {
return await Object.assign({
statusCode: statusConstants.ERROR,
payload: null,
message: "Not exist data",
});
}
return await Object.assign({
statusCode: statusConstants.SUCCESS,
payload: null,
message: "Success delete"
});
} catch(err) {
return await Object.assign({
statusCode: statusConstants.ERROR,
payload: null,
message: "rental-service database: " + err,
});
}
}
...
}
취소하기 관련 이벤트도 완성이 되었습니다. 그러면 최종 테스트를 진행해보도록 하겠습니다.
#3 테스트
결제하기 버튼을 누르고 다시 대관 내역을 들어가면 다음과 같은 결과를 볼 수 있습니다.
이어서 취소하기 버튼도 테스트 해보도록 하겠습니다.
2번째 데이터를 테스트하겠습니다.
데이터가 정상적으로 사라진 모습을 볼 수 있습니다.
이렇게 payment-service, rental-service 연동을 끝마쳤습니다. 다음 포스트에서는 게시글 관련 포스트를 작성하도록 하겠습니다.
Author And Source
이 문제에 관하여(LifeSports Application(ReactNative & Nest.js) - 19. 마이페이지 대관 내역), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@biuea/LifeSports-ApplicationReactNative-Nest.js-19.-마이페이지-대관-내역저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)