From 514000519fec10471c024f96b830c768ec032145 Mon Sep 17 00:00:00 2001 From: JWWon Date: Fri, 1 Feb 2019 04:51:18 +0900 Subject: [PATCH 01/11] Capsueling S3 upload module to services/aws folder, Apply to CreateDog component --- .../TextAutocomplete/TextAutocomplete.tsx | 2 +- src/components/module/withLoading.tsx | 1 + src/pages/Session/CreateDog/CreateDog.tsx | 38 +++++++------------ src/services/aws/s3.ts | 37 ++++++++++++++++++ src/store/actions/dog.ts | 2 +- 5 files changed, 54 insertions(+), 26 deletions(-) create mode 100644 src/services/aws/s3.ts diff --git a/src/components/module/TextAutocomplete/TextAutocomplete.tsx b/src/components/module/TextAutocomplete/TextAutocomplete.tsx index 959fd58d..ec84d11e 100644 --- a/src/components/module/TextAutocomplete/TextAutocomplete.tsx +++ b/src/components/module/TextAutocomplete/TextAutocomplete.tsx @@ -69,7 +69,7 @@ class Search extends Component { visible={this.state.showModal} onRequestClose={this.toggleModal}> ( style={{ width: 100, height: 100, + resizeMode: 'contain', transform: [{ rotate: spin }], }} /> diff --git a/src/pages/Session/CreateDog/CreateDog.tsx b/src/pages/Session/CreateDog/CreateDog.tsx index 922845a0..c9a06e99 100644 --- a/src/pages/Session/CreateDog/CreateDog.tsx +++ b/src/pages/Session/CreateDog/CreateDog.tsx @@ -14,24 +14,18 @@ import TextInput, { HandleChangeText } from 'src/components/module/TextInput'; import TextAutocomplete from 'src/components/module/TextAutocomplete'; import Selector, { HandleChangeSelector } from 'src/components/module/Selector'; // Other -import { Storage } from 'aws-amplify'; import ImagePicker from 'react-native-image-picker'; import produce from 'immer'; +import { uploadImage } from 'src/services/aws/s3'; import breeds from 'src/lib/consts/breeds.json'; interface Props extends LoadingProps { navigation: NavigationScreenProp; createDog: typeof actions.createDog; - user: ReducerState['user']; + email: ReducerState['user']['email']; } -interface State { - // ShortenDogInterface - name: string; - thumbnail: string; - breed: string; - gender: 'M' | 'F' | 'N' | ''; - // Options +interface State extends actions.ShortenDogInterface { thumbnailFile?: any; } @@ -77,21 +71,17 @@ class CreateDog extends Component { }; handleSubmit = async () => { - const { createDog, navigation, user, toggleLoading } = this.props; - const imgFile = this.state.thumbnailFile; - if (imgFile) { - await toggleLoading(); - const result = (await Storage.put( - `${user.email}/dogs/${this.state.name}/thumbnail.png`, - imgFile, - { contentType: 'image/png' } - )) as { key: string }; - await this.setState({ thumbnail: result.key }); - } - const { thumbnailFile, ...state } = this.state; + const { createDog, navigation, email, toggleLoading } = this.props; + const thumbnail = await uploadImage({ + table: 'dogs', + email, + name: this.state.name, + type: 'thumbnail', + file: this.state.thumbnailFile, + })(toggleLoading); - await toggleLoading(); - createDog(state, navigation); + const { name, breed, gender } = this.state; + await createDog({ name, breed, gender, thumbnail }, navigation); }; render() { @@ -160,7 +150,7 @@ class CreateDog extends Component { export default connect( (state: ReducerState) => ({ - user: state.user, + email: state.user.email, }), { createDog: actions.createDog } )(withLoading(CreateDog)); diff --git a/src/services/aws/s3.ts b/src/services/aws/s3.ts new file mode 100644 index 00000000..cd134ae8 --- /dev/null +++ b/src/services/aws/s3.ts @@ -0,0 +1,37 @@ +import { Storage } from 'aws-amplify'; + +/** + * FILE STRUCTURE + * PRODUCTION : {table}/{email}/{name}/{type}.png + * DEVELOPMENT : __DEV__/{table}/{email}/{name}/{type}.png + */ +interface UploadImage { + table: 'dogs' | 'users' | 'places'; + email: string; + name: string; + type: 'thumbnail' | string; + file: any; +} + +type S3ResponseType = { key: string }; +type ToggleLoading = () => void; + +export const uploadImage = ({ + email, + table, + name, + type, + file, +}: UploadImage) => async (toggleLoading: ToggleLoading) => { + if (!file) return ''; + + await toggleLoading(); + const markDev = __DEV__ ? '__DEV__/' : ''; + const S3Response = (await Storage.put( + `${markDev}${table}/${email}/${name}/${type}.png`, + file, + { contentType: 'image/png' } + )) as S3ResponseType; + await toggleLoading(); + return S3Response.key; +}; diff --git a/src/store/actions/dog.ts b/src/store/actions/dog.ts index b4a756b1..e8789de3 100644 --- a/src/store/actions/dog.ts +++ b/src/store/actions/dog.ts @@ -6,7 +6,6 @@ export interface DogInterface extends ShortenDogInterface { user: string; // FK feeds: string[]; getLikes: string[]; - gender: 'M' | 'F' | 'N'; birth?: string; // YYYY.MM.DD weight?: number; info?: string; @@ -16,6 +15,7 @@ export interface ShortenDogInterface { name: string; thumbnail: string; breed: string; + gender: 'M' | 'F' | 'N' | ''; } // *** CONSTS From 0ea6990e73b3383fea5b4216288d7ce441771bce Mon Sep 17 00:00:00 2001 From: JWWon Date: Fri, 1 Feb 2019 15:07:09 +0900 Subject: [PATCH 02/11] Apply deep linking both android & ios, Chagne android bundle identifier com.WddClient -> com.woodongdang.client.android --- android/app/BUCK | 4 +- android/app/build.gradle | 2 +- android/app/src/main/AndroidManifest.xml | 11 ++++- .../client/android}/MainActivity.java | 2 +- .../client/android}/MainApplication.java | 2 +- ios/WddClient/AppDelegate.m | 7 +++ ios/WddClient/Info.plist | 13 ++++++ src/App.tsx | 2 +- src/pages/Router.ts | 5 ++- src/pages/Session/index.ts | 43 ++++++------------- 10 files changed, 53 insertions(+), 38 deletions(-) rename android/app/src/main/java/com/{wddclient => woodongdang/client/android}/MainActivity.java (95%) rename android/app/src/main/java/com/{wddclient => woodongdang/client/android}/MainApplication.java (97%) diff --git a/android/app/BUCK b/android/app/BUCK index 12e92184..932ee17e 100644 --- a/android/app/BUCK +++ b/android/app/BUCK @@ -45,12 +45,12 @@ android_library( android_build_config( name = "build_config", - package = "com.wddclient", + package = "com.woodongdang.client.android", ) android_resource( name = "res", - package = "com.wddclient", + package = "com.woodongdang.client.android", res = "src/main/res", ) diff --git a/android/app/build.gradle b/android/app/build.gradle index 4d9dc1a9..8077669d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,7 +98,7 @@ android { buildToolsVersion '28.0.3' defaultConfig { - applicationId "com.wddclient" + applicationId "com.woodongdang.client.android" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8469451f..f5f882f5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.woodongdang.client.android"> @@ -15,12 +15,19 @@ + android:windowSoftInputMode="adjustNothing"> + + + + + + diff --git a/android/app/src/main/java/com/wddclient/MainActivity.java b/android/app/src/main/java/com/woodongdang/client/android/MainActivity.java similarity index 95% rename from android/app/src/main/java/com/wddclient/MainActivity.java rename to android/app/src/main/java/com/woodongdang/client/android/MainActivity.java index 235ecda2..268a85f4 100644 --- a/android/app/src/main/java/com/wddclient/MainActivity.java +++ b/android/app/src/main/java/com/woodongdang/client/android/MainActivity.java @@ -1,4 +1,4 @@ -package com.wddclient; +package com.woodongdang.client.android; import com.facebook.react.ReactActivity; import com.facebook.react.ReactActivityDelegate; diff --git a/android/app/src/main/java/com/wddclient/MainApplication.java b/android/app/src/main/java/com/woodongdang/client/android/MainApplication.java similarity index 97% rename from android/app/src/main/java/com/wddclient/MainApplication.java rename to android/app/src/main/java/com/woodongdang/client/android/MainApplication.java index 55c0409c..620df886 100644 --- a/android/app/src/main/java/com/wddclient/MainApplication.java +++ b/android/app/src/main/java/com/woodongdang/client/android/MainApplication.java @@ -1,4 +1,4 @@ -package com.wddclient; +package com.woodongdang.client.android; import android.app.Application; diff --git a/ios/WddClient/AppDelegate.m b/ios/WddClient/AppDelegate.m index 06fadd92..c48419ff 100644 --- a/ios/WddClient/AppDelegate.m +++ b/ios/WddClient/AppDelegate.m @@ -7,6 +7,7 @@ #import "AppDelegate.h" +#import #import #import @@ -36,4 +37,10 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( return YES; } +- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication annotation:(id)annotation +{ + return [RCTLinkingManager application:application openURL:url + sourceApplication:sourceApplication annotation:annotation]; +} @end diff --git a/ios/WddClient/Info.plist b/ios/WddClient/Info.plist index 338c3726..946e62b6 100644 --- a/ios/WddClient/Info.plist +++ b/ios/WddClient/Info.plist @@ -20,6 +20,19 @@ 0.0.1 CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + woodongdang + CFBundleURLSchemes + + woodongdang + + + CFBundleVersion 1 LSRequiresIPhoneOS diff --git a/src/App.tsx b/src/App.tsx index 4ff56450..39f1a40f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,7 +10,7 @@ const App: React.FC<{}> = () => { return ( - + ); }; diff --git a/src/pages/Router.ts b/src/pages/Router.ts index 9febbf5c..5f5822fd 100644 --- a/src/pages/Router.ts +++ b/src/pages/Router.ts @@ -9,7 +9,10 @@ export default createAppContainer( { core: CoreScreen, app: AppNavigator, - session: SessionNavigator, + session: { + screen: SessionNavigator, + path: 'session', + }, }, { initialRouteName: 'core', diff --git a/src/pages/Session/index.ts b/src/pages/Session/index.ts index 5d6a01a1..6e9ec749 100644 --- a/src/pages/Session/index.ts +++ b/src/pages/Session/index.ts @@ -16,26 +16,11 @@ import ChangePasswordScreen from './ChangePassword'; const SignUpNavigator = createMaterialTopTabNavigator( { - agreement: { - screen: AgreementScreen, - path: 'sign-up/agreement', - }, - signUpUser: { - screen: SignUpScreen, - path: 'sign-up/user', - }, - createMeta: { - screen: CreateMetaScreen, - path: 'sign-up/meta', - }, - createDog: { - screen: CreateDogScreen, - path: 'sign-up/dog', - }, - tutorial: { - screen: TutorialScreen, - path: 'sign-up/complete', - }, + agreement: AgreementScreen, + signUpUser: SignUpScreen, + createMeta: CreateMetaScreen, + createDog: CreateDogScreen, + tutorial: TutorialScreen, }, { initialRouteName: 'agreement', @@ -49,17 +34,17 @@ const SignUpNavigator = createMaterialTopTabNavigator( const ForgotPasswordNavigator = createMaterialTopTabNavigator( { - enterEmail: { + init: { screen: ForgotPasswordScreen, - path: 'forgot-password/email', + path: 'check-email', }, changePassword: { screen: ChangePasswordScreen, - path: 'forgot-password/change', + path: 'change-password/:token', }, }, { - initialRouteName: 'enterEmail', + initialRouteName: 'init', animationEnabled: true, swipeEnabled: false, tabBarOptions: { @@ -70,12 +55,12 @@ const ForgotPasswordNavigator = createMaterialTopTabNavigator( export default createStackNavigator( { - signIn: { - screen: SignInScreen, - path: 'sign-in', - }, + signIn: SignInScreen, signUp: SignUpNavigator, - forgotPassword: ForgotPasswordNavigator, + forgotPassword: { + screen: ForgotPasswordNavigator, + path: 'forgot-password', + }, }, { initialRouteName: 'signIn', From 368e1af27ea5017619792efd18962d9eee6e819f Mon Sep 17 00:00:00 2001 From: JWWon Date: Fri, 1 Feb 2019 16:12:19 +0900 Subject: [PATCH 03/11] Complete handling change password action & api --- .../Session/ChangePassword/ChangePassword.tsx | 33 ++++++++-------- src/pages/Session/CreateMeta/CreateMeta.tsx | 14 +++---- src/services/api/user.ts | 22 +++++------ src/store/actions/user.ts | 39 ++++++++++++------- src/store/reducers/user.ts | 2 +- src/store/sagas/user.ts | 33 +++++++++++++--- 6 files changed, 84 insertions(+), 59 deletions(-) diff --git a/src/pages/Session/ChangePassword/ChangePassword.tsx b/src/pages/Session/ChangePassword/ChangePassword.tsx index 733d69a7..877c3d29 100644 --- a/src/pages/Session/ChangePassword/ChangePassword.tsx +++ b/src/pages/Session/ChangePassword/ChangePassword.tsx @@ -1,10 +1,13 @@ import React, { Component } from 'react'; -import { Alert } from 'react-native'; import { NavigationScreenProp } from 'react-navigation'; - +// Redux +import { connect } from 'react-redux'; +import * as actions from 'src/store/actions/user'; +// Components import PageContainer from 'src/components/module/PageContainer'; import TextInput, { HandleChangeText } from 'src/components/module/TextInput'; import RoundButton from 'src/components/module/RoundButton'; +// Other import { validatePassword } from 'src/lib/validates/string'; interface ParamInterface { @@ -15,6 +18,7 @@ interface ParamInterface { interface Props { navigation: NavigationScreenProp; + changePassword: typeof actions.changePassword; } interface State { @@ -49,20 +53,12 @@ class ChangePassword extends Component { }; handleSubmit = () => { - // TODO: HANDLE CHANGE PASSWORD ON API - const { navigation } = this.props; - Alert.alert( - '추후에 비밀번호 변경을 추가할 계획입니다.', - '확인을 눌러 진행하세요.', - [ - { text: '예', onPress: () => navigation.navigate('signIn') }, - { - text: '아니오', - onPress: () => {}, - style: 'cancel', - }, - ] - ); + const { changePassword, navigation } = this.props; + const token = navigation.getParam('token', null); + if (token) { + const { password } = this.state; + changePassword({ password: password.value, token }, navigation); + } }; render() { @@ -102,4 +98,7 @@ class ChangePassword extends Component { } } -export default ChangePassword; +export default connect( + null, + { changePassword: actions.changePassword } +)(ChangePassword); diff --git a/src/pages/Session/CreateMeta/CreateMeta.tsx b/src/pages/Session/CreateMeta/CreateMeta.tsx index 81662a3e..c1b1ab9b 100644 --- a/src/pages/Session/CreateMeta/CreateMeta.tsx +++ b/src/pages/Session/CreateMeta/CreateMeta.tsx @@ -28,14 +28,12 @@ class CreateMeta extends Component { handleSubmit = () => { const { createMeta, navigation } = this.props; - const { gender, birth } = this.state; - createMeta( - { - gender, - birth: moment(birth).format('YYYY.MM.DD'), - }, - navigation - ); + const payload = { + gender: this.state.gender, + birth: moment(this.state.birth).format('YYYY.MM.DD'), + }; + + createMeta(payload, navigation); }; handleGenderChange = ({ name, value }: HandleChangeSelector) => { diff --git a/src/services/api/user.ts b/src/services/api/user.ts index 8cb9c6ad..f668b0d8 100644 --- a/src/services/api/user.ts +++ b/src/services/api/user.ts @@ -1,28 +1,24 @@ import axios from 'axios'; -import { - UserInterface, - SignInInterface, - SignUpInterface, - CreateMetaInterface, -} from 'src/store/actions/user'; +import * as actions from 'src/store/actions/user'; export const getUser = async () => { // *** Set token on headers before call const response = await axios.get('/user'); - return response.data as UserInterface; + return response.data as actions.UserInterface; }; -export const signIn = async (body: SignInInterface) => { +export const signIn = async (body: actions.SignInInterface) => { const response = await axios.post('/signin', body); - return response.data as UserInterface; + return response.data as actions.SignInInterface; }; -export const signUp = async (body: SignUpInterface) => { +export const signUp = async (body: actions.SignUpInterface) => { const response = await axios.post('/signup', body); - return response.data as UserInterface; + return response.data as actions.UserInterface; }; -export const createMeta = async (body: CreateMetaInterface) => { +export const updateUser = async (body: actions.UpdateInterface) => { + // *** Set token on headers before call const response = await axios.patch('/user', body); - return response.data as UserInterface; + return response.data as actions.UserInterface; }; diff --git a/src/store/actions/user.ts b/src/store/actions/user.ts index 89f232d5..fdfa3cca 100644 --- a/src/store/actions/user.ts +++ b/src/store/actions/user.ts @@ -7,8 +7,8 @@ export interface UserInterface { readonly lastLogin: string; name: string; birth: string; - gender: 'M' | 'F' | ''; - status: 'ACTIVE' | 'PAUSED' | 'TERMINATED' | ''; + gender: string; // 'M' | 'F' + status: 'ACTIVE' | 'PAUSED' | 'TERMINATED'; dogs: { [id: string]: ShortenDogInterface | DogInterface; }; @@ -17,26 +17,35 @@ export interface UserInterface { }; } +export interface UpdateInterface { + token?: string; + password?: string; + name?: string; + birth?: string; + gender?: string; // 'M' | 'F' + dogs?: { + [id: string]: ShortenDogInterface | DogInterface; + }; + places?: { + [id: string]: ShortenDogInterface; + }; +} + export interface SignInInterface { email: string; password: string; } - export interface SignUpInterface extends SignInInterface { name: string; } -export interface CreateMetaInterface { - birth: string; - gender: string; -} - // *** CONSTS export const AUTO_SIGNIN = 'user/AUTO_SIGNIN'; export const SIGNIN = 'user/SIGNIN'; export const SIGNUP = 'user/SIGNUP'; export const SIGNOUT = 'user/SIGNOUT'; -export const UPDATE_META = 'user/UPDATE_META'; +export const CREATE_META = 'user/CREATE_META'; +export const CHANGE_PASSWORD = 'user/CHANGE_PASSWORD'; export const SET_USER_REQUEST = 'user/SET_USER_REQUEST'; export const SET_USER_SUCCESS = 'user/SET_USER_SUCCESS'; @@ -66,13 +75,13 @@ export const signOut = (navigation: Navigation) => ({ navigation, }); export const createMeta = ( - payload: CreateMetaInterface, + payload: { birth: string; gender: string }, navigation: Navigation -) => ({ - type: UPDATE_META, - payload, - navigation, -}); +) => ({ type: CREATE_META, payload, navigation }); +export const changePassword = ( + payload: { password: string; token: string }, + navigation: Navigation +) => ({ type: CHANGE_PASSWORD, payload, navigation }); export const setUserRequest = () => ({ type: SET_USER_REQUEST }); export const setUserSuccess = (payload: UserInterface) => ({ diff --git a/src/store/reducers/user.ts b/src/store/reducers/user.ts index 444b4606..806af716 100644 --- a/src/store/reducers/user.ts +++ b/src/store/reducers/user.ts @@ -20,7 +20,7 @@ const initialState: UserState = { name: '', birth: '', gender: '', - status: '', + status: 'TERMINATED', dogs: {}, places: {}, }; diff --git a/src/store/sagas/user.ts b/src/store/sagas/user.ts index a77f3666..33b5223d 100644 --- a/src/store/sagas/user.ts +++ b/src/store/sagas/user.ts @@ -14,11 +14,11 @@ import * as api from 'src/services/api/user'; function* autoSignIn(action: ReturnType) { try { - yield put(actions.setUserRequest()); // *** GET TOKEN FROM STORAGE const { token, nextStep } = yield call(getUserStorage); yield call(setHeader, token); - // *** GET DATA FROM API + // *** API + yield put(actions.setUserRequest()); const data = yield call(api.getUser); yield put(actions.setUserSuccess(data)); // *** NAVIGATE @@ -51,6 +51,7 @@ function* autoSignIn(action: ReturnType) { function* signIn(action: ReturnType) { try { + // *** API yield put(actions.setUserRequest()); const data = yield call(api.signIn, action.payload); yield put(actions.setUserSuccess(data)); @@ -67,13 +68,15 @@ function* signIn(action: ReturnType) { function* signUp(action: ReturnType) { try { + // *** API yield put(actions.setUserRequest()); const data = yield call(api.signUp, action.payload); yield put(actions.setUserSuccess(data)); // *** SET TOKEN const { token } = data; - const nextStep = 'createMeta'; yield call(setHeader, token); + // *** SAVE STEP ON STORAGE + const nextStep = 'createMeta'; yield call(setUserStorage, { token, nextStep }); // *** NAVIGATE yield call(action.navigation.navigate, nextStep); @@ -92,9 +95,11 @@ function* signOut(action: ReturnType) { function* createMeta(action: ReturnType) { try { + // *** API yield put(actions.setUserRequest()); - const data = yield call(api.createMeta, action.payload); + const data = yield call(api.updateUser, action.payload); yield put(actions.setUserSuccess(data)); + // *** SAVE STEP ON STORAGE const nextStep = 'createDog'; yield call(updateUserStorage, { nextStep }); // *** NAVIGATE @@ -104,10 +109,28 @@ function* createMeta(action: ReturnType) { } } +function* changePassword(action: ReturnType) { + try { + // *** SET TOKEN + const { token } = action.payload; + yield call(setHeader, token); + yield call(setUserStorage, { token }); + // *** API + yield put(actions.setUserRequest()); + const data = yield call(api.updateUser, action.payload); + yield put(actions.setUserSuccess(data)); + // *** NAVIGATE + yield call(action.navigation.navigate, 'app'); + } catch (e) { + yield put(actions.setUserFailure(e.response)); + } +} + export default function* root() { yield takeEvery(actions.AUTO_SIGNIN, autoSignIn); yield takeEvery(actions.SIGNIN, signIn); yield takeEvery(actions.SIGNUP, signUp); yield takeEvery(actions.SIGNOUT, signOut); - yield takeEvery(actions.UPDATE_META, createMeta); + yield takeEvery(actions.CREATE_META, createMeta); + yield takeEvery(actions.CHANGE_PASSWORD, changePassword); } From fec987acc13655b6c6ead57f58a021408b256ea1 Mon Sep 17 00:00:00 2001 From: JWWon Date: Fri, 1 Feb 2019 17:11:55 +0900 Subject: [PATCH 04/11] Complete handling ForgotPassword component to send email --- .../Session/ForgotPassword/ForgotPassword.tsx | 53 +++++++++++++------ src/pages/Session/SendEmail/SendEmail.tsx | 20 +++++++ src/pages/Session/SendEmail/index.ts | 1 + src/pages/Session/index.ts | 5 ++ src/services/api/user.ts | 5 ++ src/store/actions/user.ts | 9 ++++ src/store/sagas/user.ts | 12 +++++ 7 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 src/pages/Session/SendEmail/SendEmail.tsx create mode 100644 src/pages/Session/SendEmail/index.ts diff --git a/src/pages/Session/ForgotPassword/ForgotPassword.tsx b/src/pages/Session/ForgotPassword/ForgotPassword.tsx index 2cf5418c..5b31e917 100644 --- a/src/pages/Session/ForgotPassword/ForgotPassword.tsx +++ b/src/pages/Session/ForgotPassword/ForgotPassword.tsx @@ -1,8 +1,10 @@ import React, { Component } from 'react'; -import { Alert } from 'react-native'; import produce from 'immer'; +import { connect } from 'react-redux'; import { NavigationScreenProp } from 'react-navigation'; +import * as userActions from 'src/store/actions/user'; +import { ReducerState } from 'src/store/reducers'; import PageContainer from 'src/components/module/PageContainer'; import TextInput, { HandleChangeText } from 'src/components/module/TextInput'; import RoundButton from 'src/components/module/RoundButton'; @@ -16,6 +18,8 @@ interface ParamInterface { interface Props { navigation: NavigationScreenProp; + forgotPassword: typeof userActions.forgotPassword; + user: ReducerState['user']; } interface State { @@ -30,6 +34,29 @@ class ForgotPassword extends Component { }, }; + getSnapshotBeforeUpdate(prevProps: Props) { + const { error } = this.props.user; + const prevError = prevProps.user.error; + if (error && (!prevError || error.status !== prevError.status)) + return error; + return null; + } + + componentDidUpdate( + props: Props, + state: State, + snapshot: Props['user']['error'] | null + ) { + if (snapshot) + this.setState(state => + produce(state, draft => { + delete draft.email.alert; + if (snapshot.status === 404) + draft.email.alert = snapshot.data.message; + }) + ); + } + mapEventToState = ({ value }: HandleChangeText) => { const valid = validateEmail(value); return { value, valid } as ParamInterface; @@ -43,23 +70,10 @@ class ForgotPassword extends Component { }; handleSendEmail = () => { - // TODO: REPLACE TO EMAIL VALIDATION - const { navigation } = this.props; + const { forgotPassword, navigation } = this.props; const { email } = this.state; - if (email.valid) - Alert.alert( - '추후에 이메일 인증을 추가할 계획입니다.', - '확인을 눌러 진행하세요.', - [ - { text: '예', onPress: () => navigation.navigate('changePassword') }, - { - text: '아니오', - onPress: () => {}, - style: 'cancel', - }, - ] - ); + if (email.valid) forgotPassword({ email: email.value }, navigation); else this.setState(state => produce(state, draft => { @@ -97,4 +111,9 @@ class ForgotPassword extends Component { } } -export default ForgotPassword; +export default connect( + (state: ReducerState) => ({ + user: state.user, + }), + { forgotPassword: userActions.forgotPassword } +)(ForgotPassword); diff --git a/src/pages/Session/SendEmail/SendEmail.tsx b/src/pages/Session/SendEmail/SendEmail.tsx new file mode 100644 index 00000000..6b544f21 --- /dev/null +++ b/src/pages/Session/SendEmail/SendEmail.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { View } from 'react-native'; +import { NavigationScreenProp } from 'react-navigation'; + +import PageContainer from 'src/components/module/PageContainer'; + +interface Props { + navigation: NavigationScreenProp; +} + +const SendEmail: React.FC = ({ navigation }) => ( + navigation.popToTop() }}> + + +); + +export default SendEmail; diff --git a/src/pages/Session/SendEmail/index.ts b/src/pages/Session/SendEmail/index.ts new file mode 100644 index 00000000..61a03c17 --- /dev/null +++ b/src/pages/Session/SendEmail/index.ts @@ -0,0 +1 @@ +export { default } from './SendEmail'; diff --git a/src/pages/Session/index.ts b/src/pages/Session/index.ts index 6e9ec749..7a744234 100644 --- a/src/pages/Session/index.ts +++ b/src/pages/Session/index.ts @@ -12,6 +12,7 @@ import CreateDogScreen from './CreateDog'; import TutorialScreen from './Tutorial'; import ForgotPasswordScreen from './ForgotPassword'; +import SendEmailScreen from './SendEmail'; import ChangePasswordScreen from './ChangePassword'; const SignUpNavigator = createMaterialTopTabNavigator( @@ -38,6 +39,10 @@ const ForgotPasswordNavigator = createMaterialTopTabNavigator( screen: ForgotPasswordScreen, path: 'check-email', }, + sendEmail: { + screen: SendEmailScreen, + path: 'send-email', + }, changePassword: { screen: ChangePasswordScreen, path: 'change-password/:token', diff --git a/src/services/api/user.ts b/src/services/api/user.ts index f668b0d8..25c08a04 100644 --- a/src/services/api/user.ts +++ b/src/services/api/user.ts @@ -22,3 +22,8 @@ export const updateUser = async (body: actions.UpdateInterface) => { const response = await axios.patch('/user', body); return response.data as actions.UserInterface; }; + +export const forgotPassword = async (body: { email: string }) => { + const response = await axios.post('/forgot-password', body); + return response.data as { message: string }; +}; diff --git a/src/store/actions/user.ts b/src/store/actions/user.ts index fdfa3cca..6709b9f7 100644 --- a/src/store/actions/user.ts +++ b/src/store/actions/user.ts @@ -45,6 +45,7 @@ export const SIGNIN = 'user/SIGNIN'; export const SIGNUP = 'user/SIGNUP'; export const SIGNOUT = 'user/SIGNOUT'; export const CREATE_META = 'user/CREATE_META'; +export const FORGOT_PASSWORD = 'user/FORGOT_PASSWORD'; export const CHANGE_PASSWORD = 'user/CHANGE_PASSWORD'; export const SET_USER_REQUEST = 'user/SET_USER_REQUEST'; @@ -78,6 +79,14 @@ export const createMeta = ( payload: { birth: string; gender: string }, navigation: Navigation ) => ({ type: CREATE_META, payload, navigation }); +export const forgotPassword = ( + payload: { email: string }, + navigation: Navigation +) => ({ + type: FORGOT_PASSWORD, + payload, + navigation, +}); export const changePassword = ( payload: { password: string; token: string }, navigation: Navigation diff --git a/src/store/sagas/user.ts b/src/store/sagas/user.ts index 33b5223d..12c32d15 100644 --- a/src/store/sagas/user.ts +++ b/src/store/sagas/user.ts @@ -109,6 +109,17 @@ function* createMeta(action: ReturnType) { } } +function* forgotPassword(action: ReturnType) { + try { + // *** API + yield call(api.forgotPassword, action.payload); + // *** NAVIGATE + yield call(action.navigation.navigate, 'sendEmail'); + } catch (e) { + yield put(actions.setUserFailure(e.response)); + } +} + function* changePassword(action: ReturnType) { try { // *** SET TOKEN @@ -132,5 +143,6 @@ export default function* root() { yield takeEvery(actions.SIGNUP, signUp); yield takeEvery(actions.SIGNOUT, signOut); yield takeEvery(actions.CREATE_META, createMeta); + yield takeEvery(actions.FORGOT_PASSWORD, forgotPassword); yield takeEvery(actions.CHANGE_PASSWORD, changePassword); } From ea4493eabf18b726bcbd637d529cd53757693343 Mon Sep 17 00:00:00 2001 From: JWWon Date: Sat, 2 Feb 2019 01:14:14 +0900 Subject: [PATCH 05/11] Remove 'narrow' option on PageContainer component --- .../module/PageContainer/PageContainer.styles.ts | 4 +--- src/components/module/PageContainer/PageContainer.tsx | 9 +-------- .../module/TextAutocomplete/TextAutocomplete.tsx | 3 +-- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/components/module/PageContainer/PageContainer.styles.ts b/src/components/module/PageContainer/PageContainer.styles.ts index c793797c..c0aeabc7 100644 --- a/src/components/module/PageContainer/PageContainer.styles.ts +++ b/src/components/module/PageContainer/PageContainer.styles.ts @@ -4,9 +4,7 @@ import { color, font } from 'src/theme'; export const views = StyleSheet.create({ container: { flex: 1 }, - contentWrapper: { flex: 1 }, - wrapperNormal: { marginHorizontal: '10.7%' }, - wrapperNarrow: { marginHorizontal: '5.3%' }, + contentWrapper: { flex: 1, marginHorizontal: '5.3%' }, topWrapper: { marginTop: 20, marginHorizontal: '4%', diff --git a/src/components/module/PageContainer/PageContainer.tsx b/src/components/module/PageContainer/PageContainer.tsx index 0df7fdac..c1f2de40 100644 --- a/src/components/module/PageContainer/PageContainer.tsx +++ b/src/components/module/PageContainer/PageContainer.tsx @@ -34,7 +34,6 @@ interface Props { disable?: boolean; }; // options - narrow?: boolean; [x: string]: any; } @@ -46,7 +45,6 @@ const PageContainer: React.FC = ({ right, center, bottom, - narrow, ...scrollOptions }) => ( @@ -70,12 +68,7 @@ const PageContainer: React.FC = ({ )} - + {title && ( {title} diff --git a/src/components/module/TextAutocomplete/TextAutocomplete.tsx b/src/components/module/TextAutocomplete/TextAutocomplete.tsx index ec84d11e..9bf7da9b 100644 --- a/src/components/module/TextAutocomplete/TextAutocomplete.tsx +++ b/src/components/module/TextAutocomplete/TextAutocomplete.tsx @@ -70,8 +70,7 @@ class Search extends Component { onRequestClose={this.toggleModal}> + scrollEnabled={false}> Date: Sat, 2 Feb 2019 02:04:19 +0900 Subject: [PATCH 06/11] Change production baseURL to api.woodongdang.com, Change units of PageContainer views --- src/components/base/Core.tsx | 6 +++++- .../module/PageContainer/PageContainer.styles.ts | 11 ++++++----- .../module/PageContainer/PageContainer.tsx | 14 +++++++------- src/pages/Session/SignIn/SignIn.tsx | 8 ++++---- src/services/api/axios.ts | 2 +- 5 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/components/base/Core.tsx b/src/components/base/Core.tsx index f3bae9f1..4e9f8a67 100644 --- a/src/components/base/Core.tsx +++ b/src/components/base/Core.tsx @@ -18,7 +18,11 @@ class Core extends PureComponent { super(props); configAxios(); Amplify.configure(awsconfig); - props.autoSignIn(this.props.navigation); + } + + componentDidMount() { + const { autoSignIn, navigation } = this.props; + autoSignIn(navigation); } render() { diff --git a/src/components/module/PageContainer/PageContainer.styles.ts b/src/components/module/PageContainer/PageContainer.styles.ts index c0aeabc7..6ca57a1f 100644 --- a/src/components/module/PageContainer/PageContainer.styles.ts +++ b/src/components/module/PageContainer/PageContainer.styles.ts @@ -6,9 +6,9 @@ export const views = StyleSheet.create({ container: { flex: 1 }, contentWrapper: { flex: 1, marginHorizontal: '5.3%' }, topWrapper: { - marginTop: 20, + marginTop: 15, marginHorizontal: '4%', - height: 26, + height: 21, flexDirection: 'row', alignItems: 'center', }, @@ -45,18 +45,19 @@ export const views = StyleSheet.create({ export const texts = StyleSheet.create({ top: { color: color.blackOpacity, - fontSize: font.size.large, + fontSize: 16, }, center: { color: color.black, fontSize: font.size.large, }, bottomText: { - color: color.gary55, + color: `${color.gary55}CC`, fontSize: font.size.small, + textDecorationLine: 'underline', }, bottomDiff: { - color: color.black33, + color: `${color.black33}7F`, fontSize: font.size.small, }, bottomBox: { diff --git a/src/components/module/PageContainer/PageContainer.tsx b/src/components/module/PageContainer/PageContainer.tsx index c1f2de40..f6c0c18a 100644 --- a/src/components/module/PageContainer/PageContainer.tsx +++ b/src/components/module/PageContainer/PageContainer.tsx @@ -97,19 +97,19 @@ const PageContainer: React.FC = ({ ) : ( - - {bottom.text} - {bottom.diffText && bottom.handleDiffPress && ( - {bottom.diffText} + {bottom.diffText} )} + + {bottom.text} + ))} diff --git a/src/pages/Session/SignIn/SignIn.tsx b/src/pages/Session/SignIn/SignIn.tsx index 05951551..ce25653f 100644 --- a/src/pages/Session/SignIn/SignIn.tsx +++ b/src/pages/Session/SignIn/SignIn.tsx @@ -104,10 +104,10 @@ class SignIn extends Component { return ( navigation.navigate('forgotPassword'), - diffText: '회원가입 하기', - handleDiffPress: () => navigation.navigate('signUp'), + text: '회원가입', + handlePress: () => navigation.navigate('signUp'), + diffText: '비밀번호를 잊으셨나요?', + handleDiffPress: () => navigation.navigate('forgotPassword'), }} right={{ text: '건너뛰기', diff --git a/src/services/api/axios.ts b/src/services/api/axios.ts index 87fb0c35..79399b89 100644 --- a/src/services/api/axios.ts +++ b/src/services/api/axios.ts @@ -11,5 +11,5 @@ export function removeHeader() { export default function configAxios() { axios.defaults.baseURL = __DEV__ ? 'http://localhost:8080' - : 'http://ec2-13-209-98-100.ap-northeast-2.compute.amazonaws.com'; + : 'http://api.woodongdang.com'; } From 95eadfcf58c7ef10b56c064e4d2847e244406bd8 Mon Sep 17 00:00:00 2001 From: JWWon Date: Sat, 2 Feb 2019 02:11:15 +0900 Subject: [PATCH 07/11] Add ic_agree on Agreement component --- src/lib/icons/ic_agree.png | Bin 0 -> 4172 bytes src/lib/icons/ic_agree@2x.png | Bin 0 -> 9092 bytes src/lib/icons/ic_agree@3x.png | Bin 0 -> 14342 bytes src/pages/Session/Agreement/Agreement.styles.ts | 10 +++++++++- src/pages/Session/Agreement/Agreement.tsx | 8 ++++++-- 5 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 src/lib/icons/ic_agree.png create mode 100644 src/lib/icons/ic_agree@2x.png create mode 100644 src/lib/icons/ic_agree@3x.png diff --git a/src/lib/icons/ic_agree.png b/src/lib/icons/ic_agree.png new file mode 100644 index 0000000000000000000000000000000000000000..c8e400d90feeeae423938f4643e16af160ca4635 GIT binary patch literal 4172 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 A6951J literal 0 HcmV?d00001 diff --git a/src/pages/Session/Agreement/Agreement.styles.ts b/src/pages/Session/Agreement/Agreement.styles.ts index c3ad4cea..427338d9 100644 --- a/src/pages/Session/Agreement/Agreement.styles.ts +++ b/src/pages/Session/Agreement/Agreement.styles.ts @@ -1,5 +1,5 @@ import { StyleSheet } from 'react-native'; -import { font, color } from 'src/theme'; +import { color } from 'src/theme'; export const views = StyleSheet.create({ agreeAll: { @@ -18,6 +18,14 @@ export const views = StyleSheet.create({ agreeAllInactive: { borderColor: color.grayDA, }, + agreeIcon: { + position: 'absolute', + top: -50, + right: '7.3%', + width: 58, + height: 54, + resizeMode: 'contain', + }, }); export const texts = StyleSheet.create({ diff --git a/src/pages/Session/Agreement/Agreement.tsx b/src/pages/Session/Agreement/Agreement.tsx index 42c2f750..6f3b0403 100644 --- a/src/pages/Session/Agreement/Agreement.tsx +++ b/src/pages/Session/Agreement/Agreement.tsx @@ -1,6 +1,6 @@ import React, { Component } from 'react'; -import produce, { isDraft } from 'immer'; -import { TouchableOpacity, Text } from 'react-native'; +import produce from 'immer'; +import { TouchableOpacity, Text, Image } from 'react-native'; import { NavigationScreenProp } from 'react-navigation'; import PageContainer from 'src/components/module/PageContainer'; @@ -56,6 +56,10 @@ class Agreement extends Component { ]}> 아래 약관에 모두 동의합니다. + ); From 8247e73c0230f3a3cce01c54228d99b62b349765 Mon Sep 17 00:00:00 2001 From: JWWon Date: Sat, 2 Feb 2019 02:13:20 +0900 Subject: [PATCH 08/11] Handle submit button on ChangePassword component --- src/pages/Session/ChangePassword/ChangePassword.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/Session/ChangePassword/ChangePassword.tsx b/src/pages/Session/ChangePassword/ChangePassword.tsx index 877c3d29..659f2052 100644 --- a/src/pages/Session/ChangePassword/ChangePassword.tsx +++ b/src/pages/Session/ChangePassword/ChangePassword.tsx @@ -86,7 +86,9 @@ class ChangePassword extends Component { name="passwordCheck" value={passwordCheck.value} secureTextEntry={true} + returnKeyType="send" handleChange={this.handleChange} + onSubmitEditing={this.handleSubmit} /> Date: Sat, 2 Feb 2019 02:28:47 +0900 Subject: [PATCH 09/11] Apply goBack button on PageContainer's navigation bar section --- .../PageContainer/PageContainer.styles.ts | 5 ++++ .../module/PageContainer/PageContainer.tsx | 25 +++++++++++++----- src/lib/icons/ic_back.png | Bin 0 -> 229 bytes src/lib/icons/ic_back@2x.png | Bin 0 -> 446 bytes src/lib/icons/ic_back@3x.png | Bin 0 -> 609 bytes .../Session/ChangePassword/ChangePassword.tsx | 5 +--- src/pages/Session/CreateDog/CreateDog.tsx | 2 +- src/pages/Session/CreateMeta/CreateMeta.tsx | 2 +- .../Session/ForgotPassword/ForgotPassword.tsx | 2 +- 9 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 src/lib/icons/ic_back.png create mode 100644 src/lib/icons/ic_back@2x.png create mode 100644 src/lib/icons/ic_back@3x.png diff --git a/src/components/module/PageContainer/PageContainer.styles.ts b/src/components/module/PageContainer/PageContainer.styles.ts index 6ca57a1f..de9ddd8d 100644 --- a/src/components/module/PageContainer/PageContainer.styles.ts +++ b/src/components/module/PageContainer/PageContainer.styles.ts @@ -40,6 +40,11 @@ export const views = StyleSheet.create({ titleWrapper: { marginVertical: 40, }, + backIcon: { + width: 18, + height: 16, + resizeMode: 'contain', + }, }); export const texts = StyleSheet.create({ diff --git a/src/components/module/PageContainer/PageContainer.tsx b/src/components/module/PageContainer/PageContainer.tsx index f6c0c18a..3dd5696b 100644 --- a/src/components/module/PageContainer/PageContainer.tsx +++ b/src/components/module/PageContainer/PageContainer.tsx @@ -1,10 +1,12 @@ import React, { ReactNode } from 'react'; +import { NavigationScreenProp } from 'react-navigation'; import { SafeAreaView, TouchableOpacity, Text, ScrollView, View, + Image, } from 'react-native'; import { views, texts } from './PageContainer.styles'; @@ -16,8 +18,7 @@ interface Props { subtitle?: string; // top left?: { - text: string; - handlePress: () => void; + navigation: NavigationScreenProp; }; right?: { text: string; @@ -37,6 +38,20 @@ interface Props { [x: string]: any; } +const NavbarLeft: React.FC = ({ navigation }) => { + function navBack() { + navigation.goBack(null); + } + return ( + + + + ); +}; + const PageContainer: React.FC = ({ children, title, @@ -54,11 +69,7 @@ const PageContainer: React.FC = ({ {center} )} - {left && ( - - {left.text} - - )} + {left && } {right && ( !Kcann{UmXXznuGpE~WgR zyi8{QD}IN}x<6hVr#CT7HDLPatZf#NaCMrhc=}i zXWd`C7Zeh+B-xCgn@?cm3SY(Ox4`rMiZrP!3Y-dm=iF*r#mKwIGuPR-k!{|hZ41>L dm_4=l8M(WK7EV?QN&-5S!PC{xWt~$(698J+RV)Ai literal 0 HcmV?d00001 diff --git a/src/lib/icons/ic_back@2x.png b/src/lib/icons/ic_back@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..17dd8a0cad4774df64beb1d97ce6a0bf5c8ab349 GIT binary patch literal 446 zcmV;v0YUzWP)Px$cu7P-R9Fekn6FC&K^TV59~dlJ6byzd2POrZ!FV8OvS_<#vtAJKe=yiD$h8Y% z7ONP=U{DY&A}E50zkObAK3R-&6W$jdW?7i;9%lD;zTH}Fu(X-}4(o%JDL;FQl0S*H z^5yd@ZZh>diS6c>DL-?SlAlRzzkK=picO}zC9zI^nesE2DEXSidW^&6e* ze}H2c1&cJWcmRo=!WdX&7K=NO*g1@YMW(U128mt51XyGeiwlt06-1{hOjsUiM_xg*yI2ciD?H|wy{Da#3bj!BQ6WtAGKO; z(QWpZSUq(dR+m+V)_({I+CEAor`SG(`+EJ(qq=>mGqFl_M`Wz99)Jk#^+=-J; o<90=G!$~Y$_LAUAoG3Z|0`97Q?-KtVs{jB107*qoM6N<$f~`Hj*#H0l literal 0 HcmV?d00001 diff --git a/src/lib/icons/ic_back@3x.png b/src/lib/icons/ic_back@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..5cda79a9a07dd4b6099e9759c2758db3ba344774 GIT binary patch literal 609 zcmV-n0-pVeP)Px%8%ab#RA>e5ny*U)K^(=;ABY@+ARK5g7*4QQt_X4_lgVb%X7eA=a(1K5XfkQh zWDW8di1~jjbYVs2KkQE_d8qhN&$ZO;ykX1~9 z<`EKP8~KEgFJA&MDglt8SJC2ZR5#%0@d)z?j4ul;Kds{n~Aa(~hZgTvz3J(6EJ8*_$&V;OD z4m6z3sma>911|yOzdP_(+c4dMhoC0aY+cF=u+=$S972MX*V{j|z4ZNjNrHIt+qM!ZQ+V z>;kUqgj<=6ugi*~Q-2KUnYDE43Cb9R?AKnpr1+qR6tAXn`e+&P`Y2xr?AAwBb2TJ4 zke*q~6>q_gBWVY)mU|ckOW&yzz;ag#{{YK~T?#g7Hf_sjfGTt;xS7Ll=a_0PX_tZ% v47=T(s@d43XzGMxxdJFITJ1xnuC4w83=c2aWO{hz00000NkvXXu0mjfr}hln literal 0 HcmV?d00001 diff --git a/src/pages/Session/ChangePassword/ChangePassword.tsx b/src/pages/Session/ChangePassword/ChangePassword.tsx index 659f2052..75460ce8 100644 --- a/src/pages/Session/ChangePassword/ChangePassword.tsx +++ b/src/pages/Session/ChangePassword/ChangePassword.tsx @@ -69,10 +69,7 @@ class ChangePassword extends Component { navigation.navigate('signIn'), - }} + left={{ navigation }} scrollEnabled={false}> { <> navigation.goBack(null) }} + left={{ navigation }} right={{ text: '취소', handlePress: () => navigation.popToTop() }} bottom={{ text: '다음', diff --git a/src/pages/Session/CreateMeta/CreateMeta.tsx b/src/pages/Session/CreateMeta/CreateMeta.tsx index c1b1ab9b..43337a44 100644 --- a/src/pages/Session/CreateMeta/CreateMeta.tsx +++ b/src/pages/Session/CreateMeta/CreateMeta.tsx @@ -51,7 +51,7 @@ class CreateMeta extends Component { navigation.goBack(null) }} + left={{ navigation }} right={{ text: '취소', handlePress: () => navigation.popToTop() }} bottom={{ text: '다음', diff --git a/src/pages/Session/ForgotPassword/ForgotPassword.tsx b/src/pages/Session/ForgotPassword/ForgotPassword.tsx index 5b31e917..607a4876 100644 --- a/src/pages/Session/ForgotPassword/ForgotPassword.tsx +++ b/src/pages/Session/ForgotPassword/ForgotPassword.tsx @@ -90,7 +90,7 @@ class ForgotPassword extends Component { navigation.goBack(null) }} + left={{ navigation }} scrollEnabled={false}> Date: Sat, 2 Feb 2019 04:19:58 +0900 Subject: [PATCH 10/11] Complete handling focus next input when hit return on keyboard --- src/components/module/TextInput/TextInput.tsx | 48 ++++++++++++++++--- .../Session/ChangePassword/ChangePassword.tsx | 9 ++++ src/pages/Session/SignIn/SignIn.tsx | 10 +++- src/pages/Session/SignUp/SignUp.tsx | 15 ++++++ 4 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/components/module/TextInput/TextInput.tsx b/src/components/module/TextInput/TextInput.tsx index 3e08635a..1ca53819 100644 --- a/src/components/module/TextInput/TextInput.tsx +++ b/src/components/module/TextInput/TextInput.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent as Component } from 'react'; +import React, { PureComponent } from 'react'; import { View, Text, @@ -8,15 +8,21 @@ import { } from 'react-native'; import { HandleChangeText } from './index'; -import { inputs, texts } from './TextInput.styles'; +import { inputs as inputStyle, texts } from './TextInput.styles'; import ModuleContainer from 'src/components/module/ModuleContainer'; +interface InputsInterface { + [key: string]: React.RefObject; +} + interface Props { label: string; name: string; value?: string; alert?: string; + inputs?: InputsInterface; handleChange: (data: HandleChangeText) => void; + returnKeyType?: 'next' | 'done' | 'send' | 'search'; [x: string]: any; } @@ -24,7 +30,7 @@ interface State { isFocus: boolean; } -class TextInput extends Component { +class TextInput extends PureComponent { state: State = { isFocus: false }; handleFocus = (e: NativeSyntheticEvent) => { @@ -40,8 +46,31 @@ class TextInput extends Component { handleChange({ name, value }); }; + handleFocusNext = () => { + const { inputs, name } = this.props; + if (inputs) { + const keyArray = Object.keys(inputs); + keyArray.map((key, index) => { + if (key === name) { + // select next input + const { current } = inputs[keyArray[index + 1]]; + if (current) current.focus(); + } + }); + } + }; + render() { - const { label, value, alert, ...options } = this.props; + const { + name, + label, + value, + alert, + inputs, + returnKeyType, + ...options + } = this.props; + return ( { onChangeText={this.handleChangeWithName} onFocus={options.handleFocus || this.handleFocus} onBlur={this.handleBlur} + returnKeyType={returnKeyType || 'default'} autoCapitalize="none" autoCorrect={false} style={[ - inputs.text, - inputs[this.state.isFocus ? 'focused' : 'unFocused'], + inputStyle.text, + inputStyle[this.state.isFocus ? 'focused' : 'unFocused'], ]} {...options} + {...inputs && { ref: inputs[name] }} + {...(returnKeyType === 'next' + ? { + onSubmitEditing: this.handleFocusNext, + } + : {})} /> {alert && {alert}} diff --git a/src/pages/Session/ChangePassword/ChangePassword.tsx b/src/pages/Session/ChangePassword/ChangePassword.tsx index 75460ce8..60ff798b 100644 --- a/src/pages/Session/ChangePassword/ChangePassword.tsx +++ b/src/pages/Session/ChangePassword/ChangePassword.tsx @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import { TextInput as Input } from 'react-native'; import { NavigationScreenProp } from 'react-navigation'; // Redux import { connect } from 'react-redux'; @@ -27,6 +28,11 @@ interface State { } class ChangePassword extends Component { + private inputs = { + password: React.createRef(), + passwordCheck: React.createRef(), + }; + state: State = { password: { value: '', @@ -76,6 +82,8 @@ class ChangePassword extends Component { name="password" value={password.value} secureTextEntry={true} + returnKeyType="next" + inputs={this.inputs} handleChange={this.handleChange} /> { value={passwordCheck.value} secureTextEntry={true} returnKeyType="send" + inputs={this.inputs} handleChange={this.handleChange} onSubmitEditing={this.handleSubmit} /> diff --git a/src/pages/Session/SignIn/SignIn.tsx b/src/pages/Session/SignIn/SignIn.tsx index ce25653f..28bd2771 100644 --- a/src/pages/Session/SignIn/SignIn.tsx +++ b/src/pages/Session/SignIn/SignIn.tsx @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import produce from 'immer'; -import { Image } from 'react-native'; +import { Image, TextInput as Input } from 'react-native'; import { connect } from 'react-redux'; import { NavigationScreenProp } from 'react-navigation'; @@ -30,6 +30,11 @@ interface State { } class SignIn extends Component { + private inputs = { + email: React.createRef(), + password: React.createRef(), + }; + state: State = { email: { value: '', valid: false }, password: { value: '', valid: false }, @@ -124,6 +129,8 @@ class SignIn extends Component { value={email.value} alert={email.alert} keyboardType="email-address" + returnKeyType="next" + inputs={this.inputs} handleChange={this.handleChange} /> { alert={password.alert} secureTextEntry={true} returnKeyType="send" + inputs={this.inputs} handleChange={this.handleChange} onSubmitEditing={this.handleSignIn} /> diff --git a/src/pages/Session/SignUp/SignUp.tsx b/src/pages/Session/SignUp/SignUp.tsx index 6b40c3d3..a27de85c 100644 --- a/src/pages/Session/SignUp/SignUp.tsx +++ b/src/pages/Session/SignUp/SignUp.tsx @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import { TextInput as Input } from 'react-native'; import { connect } from 'react-redux'; import produce from 'immer'; import { NavigationScreenProp } from 'react-navigation'; @@ -28,6 +29,13 @@ interface State { } class SignUp extends Component { + private inputs = { + name: React.createRef(), + email: React.createRef(), + password: React.createRef(), + passwordCheck: React.createRef(), + }; + state: State = { name: { value: '', valid: false }, email: { value: '', valid: false }, @@ -98,6 +106,8 @@ class SignUp extends Component { name="name" value={name.value} alert={name.alert} + returnKeyType="next" + inputs={this.inputs} handleChange={this.handleChange} /> { value={email.value} alert={email.alert} keyboardType="email-address" + returnKeyType="next" + inputs={this.inputs} handleChange={this.handleChange} /> { value={password.value} alert={password.alert} secureTextEntry={true} + returnKeyType="next" + inputs={this.inputs} handleChange={this.handleChange} /> { alert={passwordCheck.alert} secureTextEntry={true} returnKeyType="send" + inputs={this.inputs} handleChange={this.handleChange} onSubmitEditing={this.handleSignUp} /> From 235293aec62a727791399d9b7e952fdd0cdf6527 Mon Sep 17 00:00:00 2001 From: JWWon Date: Sat, 2 Feb 2019 04:36:25 +0900 Subject: [PATCH 11/11] Update project version to 0.0.2 --- android/app/build.gradle | 4 ++-- ios/WddClient/Info.plist | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 8077669d..503482e6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -101,8 +101,8 @@ android { applicationId "com.woodongdang.client.android" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" + versionCode 2 + versionName "0.0.2" ndk { abiFilters "armeabi-v7a", "x86" } diff --git a/ios/WddClient/Info.plist b/ios/WddClient/Info.plist index 946e62b6..bff44cbf 100644 --- a/ios/WddClient/Info.plist +++ b/ios/WddClient/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.0.1 + 0.0.2 CFBundleSignature ???? CFBundleURLTypes @@ -34,7 +34,7 @@ CFBundleVersion - 1 + 2 LSRequiresIPhoneOS NSAppTransportSecurity diff --git a/package.json b/package.json index b3b96ba4..a1f12ca3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wdd-client", - "version": "0.0.1", + "version": "0.0.2", "private": true, "scripts": { "start": "node node_modules/react-native/local-cli/cli.js start",