Fakultas Ilmu Komputer UI
Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
ppl-fasilkom-ui
2021
Kelas D
PT Gizi Sehat - Dietela
Dietela Mobile
Commits
b87e5675
Commit
b87e5675
authored
Apr 26, 2021
by
Doan Andreas Nathanael
Browse files
Manual Registration API Integration
parent
717e5abe
Changes
21
Hide whitespace changes
Inline
Side-by-side
src/__mocks__/auth.ts
View file @
b87e5675
import
{
LoginResponse
}
from
'
services/auth/models
'
;
export
const
validRegistrationValues
:
{
[
_
:
string
]:
any
}
=
{
name
:
'
Doan Didinding
'
,
email
:
'
doan@dietela.com
'
,
password1
:
'
g8ake1afig
'
,
password2
:
'
g8ake1afig
'
,
};
export
const
invalidRegistrationValues
:
{
[
_
:
string
]:
any
}
=
{
name
:
'
Doan Didinding
'
,
email
:
'
doan@dietela.com
'
,
password1
:
'
12345678
'
,
password2
:
'
12345678
'
,
};
export
const
authResponse
:
LoginResponse
=
{
access_token
:
'
ax41faf
'
,
refresh_token
:
'
9tka0kfa
'
,
user
:
{
id
:
1
,
email
:
validRegistrationValues
.
email
,
name
:
validRegistrationValues
.
name
,
},
};
src/app/index.tsx
View file @
b87e5675
...
...
@@ -19,7 +19,7 @@ const App: FC = () => {
<
ContextProvider
>
<
NavigationContainer
>
<
Stack
.
Navigator
initialRouteName
=
{
ROUTES
.
logi
n
}
initialRouteName
=
{
ROUTES
.
registratio
n
}
screenOptions
=
{
screenOptions
}
>
{
navigation
.
map
((
nav
,
i
)
=>
(
<
Stack
.
Screen
...
...
src/components/core/BigButton/index.tsx
View file @
b87e5675
...
...
@@ -13,7 +13,7 @@ const BigButton: FC<Props> = ({
testID
,
})
=>
(
<
Button
titleStyle
=
{
[
typographyStyles
.
overlineBig
,
styles
.
titleStyle
]
}
titleStyle
=
{
[
typographyStyles
.
bodyMedium
,
styles
.
titleStyle
]
}
disabledTitleStyle
=
{
styles
.
disabledStyle
}
disabled
=
{
disabled
}
buttonStyle
=
{
styles
.
containerStyle
}
...
...
src/components/core/BigButton/styles.ts
View file @
b87e5675
...
...
@@ -2,7 +2,7 @@ import { StyleSheet } from 'react-native';
import
{
colors
}
from
'
styles
'
;
export
const
styles
=
StyleSheet
.
create
({
titleStyle
:
{
color
:
colors
.
textBlack
},
titleStyle
:
{
color
:
colors
.
textBlack
,
textTransform
:
'
capitalize
'
},
containerStyle
:
{
padding
:
12
,
overflow
:
'
hidden
'
,
...
...
src/components/core/Link/index.test.tsx
0 → 100644
View file @
b87e5675
import
React
from
'
react
'
;
import
{
render
}
from
'
@testing-library/react-native
'
;
import
Link
from
'
.
'
;
import
{
colors
}
from
'
styles
'
;
describe
(
'
Link
'
,
()
=>
{
it
(
'
renders correctly
'
,
()
=>
{
render
(<
Link
title
=
"hai"
/>);
});
it
(
'
renders correctly given color
'
,
()
=>
{
render
(<
Link
title
=
"hai"
color
=
{
colors
.
primary
}
/>);
});
});
src/components/core/Link/index.tsx
0 → 100644
View file @
b87e5675
import
React
,
{
FC
}
from
'
react
'
;
import
{
StyleSheet
}
from
'
react-native
'
;
import
{
Button
,
ButtonProps
}
from
'
react-native-elements
'
;
import
{
colors
}
from
'
styles
'
;
interface
Props
extends
ButtonProps
{
title
:
string
;
color
?:
string
;
}
const
Link
:
FC
<
Props
>
=
({
title
,
color
,
...
props
})
=>
{
const
styles
=
stylesWithColor
(
color
);
return
(
<
Button
title
=
{
title
}
type
=
"clear"
buttonStyle
=
{
styles
.
buttonPadding
}
titleStyle
=
{
styles
.
title
}
{
...
props
}
/>
);
};
const
stylesWithColor
=
(
color
?:
string
)
=>
StyleSheet
.
create
({
buttonPadding
:
{
padding
:
5
,
},
title
:
{
textDecorationLine
:
'
underline
'
,
color
:
color
?
color
:
colors
.
buttonYellow
,
},
});
export
default
Link
;
src/components/core/index.ts
View file @
b87e5675
...
...
@@ -2,6 +2,7 @@ export { default as AutoImage } from './AutoImage';
export
{
default
as
BigButton
}
from
'
./BigButton
'
;
export
{
default
as
CarouselPagination
}
from
'
./CarouselPagination
'
;
export
{
default
as
InfoCard
}
from
'
./InfoCard
'
;
export
{
default
as
Link
}
from
'
./Link
'
;
export
{
default
as
Loader
}
from
'
./Loader
'
;
export
{
default
as
ResultCard
}
from
'
./ResultCard
'
;
export
{
default
as
Statistic
}
from
'
./Statistic
'
;
...
...
src/provider/UserContext/index.ts
View file @
b87e5675
...
...
@@ -4,8 +4,8 @@ import { GoogleSignin } from '@react-native-google-signin/google-signin';
import
{
Toast
}
from
'
components/core
'
;
import
CACHE_KEYS
from
'
constants/cacheKeys
'
;
import
{
removeCache
,
getCache
,
setCache
}
from
'
utils/cache
'
;
import
{
googleLoginApi
}
from
'
services/auth
'
;
import
{
User
}
from
'
services/auth/models
'
;
import
{
googleLoginApi
,
signupApi
}
from
'
services/auth
'
;
import
{
User
,
RegistrationRequest
}
from
'
services/auth/models
'
;
import
{
set401Callback
,
setAuthHeader
,
resetAuthHeader
}
from
'
services/api
'
;
import
{
iUserContext
}
from
'
./types
'
;
...
...
@@ -38,8 +38,18 @@ export const useUserContext = (): iUserContext => {
}
},
[]);
// TODO
const
signup
=
async
()
=>
{};
const
signup
=
async
(
registerData
:
RegistrationRequest
)
=>
{
const
response
=
await
signupApi
(
registerData
);
if
(
response
.
success
&&
response
.
data
)
{
await
setCache
(
CACHE_KEYS
.
authToken
,
response
.
data
?.
access_token
);
await
setCache
(
CACHE_KEYS
.
refreshToken
,
response
.
data
?.
refresh_token
);
setUser
(
response
.
data
.
user
);
}
return
response
;
};
// TODO
const
login
=
async
()
=>
{};
...
...
src/provider/UserContext/types.ts
View file @
b87e5675
import
{
User
}
from
'
services/auth/models
'
;
import
{
ApiResponse
}
from
'
services/api
'
;
import
{
LoginResponse
,
RegistrationRequest
,
User
}
from
'
services/auth/models
'
;
export
interface
iUserContext
{
user
:
User
;
isAuthenticated
:
boolean
;
isLoading
:
boolean
;
signup
:
(
)
=>
Promise
<
void
>
;
signup
:
(
data
:
RegistrationRequest
)
=>
ApiResponse
<
LoginResponse
>
;
login
:
()
=>
Promise
<
void
>
;
loginWithGoogle
:
()
=>
Promise
<
void
>
;
logout
:
()
=>
Promise
<
void
>
;
...
...
src/scenes/auth/ManualRegistrationPage/index.test.tsx
0 → 100644
View file @
b87e5675
import
React
from
'
react
'
;
import
{
render
,
fireEvent
,
waitFor
}
from
'
utils/testing
'
;
import
*
as
ROUTES
from
'
constants/routes
'
;
import
axios
from
'
axios
'
;
import
ManualRegistrationPage
from
'
.
'
;
import
{
textField
}
from
'
./schema
'
;
import
{
authResponse
,
validRegistrationValues
,
invalidRegistrationValues
,
}
from
'
__mocks__/auth
'
;
jest
.
mock
(
'
react-native-toast-message
'
);
jest
.
mock
(
'
axios
'
);
const
mockAxios
=
axios
as
jest
.
Mocked
<
typeof
axios
>
;
describe
(
'
ManualRegistrationPage
'
,
()
=>
{
it
(
'
renders correctly
'
,
()
=>
{
render
(<
ManualRegistrationPage
/>,
ROUTES
.
registration
);
});
it
(
'
success when field is valid and submit success
'
,
async
()
=>
{
const
signupApi
=
()
=>
Promise
.
resolve
({
status
:
201
,
data
:
authResponse
,
});
mockAxios
.
request
.
mockImplementationOnce
(
signupApi
);
const
{
getByPlaceholderText
,
queryByText
,
getByTestId
}
=
render
(
<
ManualRegistrationPage
/>,
ROUTES
.
registration
,
);
textField
.
map
(({
name
,
placeholder
})
=>
{
const
formField
=
getByPlaceholderText
(
placeholder
as
string
);
fireEvent
.
changeText
(
formField
,
validRegistrationValues
[
name
]);
});
const
submitButton
=
getByTestId
(
'
submitButton
'
);
await
waitFor
(()
=>
fireEvent
.
press
(
submitButton
));
const
toastWarning
=
queryByText
(
/Profile/i
);
expect
(
toastWarning
).
toBeTruthy
();
});
it
(
'
fails when field is valid and submit fails
'
,
async
()
=>
{
const
signupApi
=
()
=>
Promise
.
reject
({
status
:
400
,
response
:
{
error
:
'
error
'
,
},
});
mockAxios
.
request
.
mockImplementationOnce
(
signupApi
);
const
{
getByPlaceholderText
,
queryByText
,
getByTestId
}
=
render
(
<
ManualRegistrationPage
/>,
ROUTES
.
registration
,
);
textField
.
map
(({
name
,
placeholder
})
=>
{
const
formField
=
getByPlaceholderText
(
placeholder
as
string
);
fireEvent
.
changeText
(
formField
,
validRegistrationValues
[
name
]);
});
const
submitButton
=
getByTestId
(
'
submitButton
'
);
await
waitFor
(()
=>
fireEvent
.
press
(
submitButton
));
const
nextPageText
=
queryByText
(
/Profile/i
);
expect
(
nextPageText
).
toBeFalsy
();
});
it
(
'
fails when field is invalid and submit success
'
,
async
()
=>
{
const
alreadyRegistered
=
'
Email is already registered
'
;
const
signupApi
=
()
=>
Promise
.
reject
({
status
:
400
,
response
:
{
error
:
{
name
:
'
Wrong name
'
,
email
:
alreadyRegistered
,
password1
:
'
Wrong password
'
,
password2
:
'
Wrong password
'
,
},
},
});
mockAxios
.
request
.
mockImplementationOnce
(
signupApi
);
const
{
getByPlaceholderText
,
queryByText
,
getByTestId
}
=
render
(
<
ManualRegistrationPage
/>,
ROUTES
.
registration
,
);
textField
.
map
(({
name
,
placeholder
})
=>
{
const
formField
=
getByPlaceholderText
(
placeholder
as
string
);
fireEvent
.
changeText
(
formField
,
invalidRegistrationValues
[
name
]);
});
const
submitButton
=
getByTestId
(
'
submitButton
'
);
await
waitFor
(()
=>
fireEvent
.
press
(
submitButton
));
const
nextPageText
=
queryByText
(
/Profile/i
);
expect
(
nextPageText
).
toBeFalsy
();
});
});
src/scenes/
common
/ManualRegistrationPage/index.tsx
→
src/scenes/
auth
/ManualRegistrationPage/index.tsx
View file @
b87e5675
import
React
,
{
FC
}
from
'
react
'
;
import
{
useForm
}
from
'
hooks
'
;
import
React
,
{
FC
,
useContext
}
from
'
react
'
;
import
{
useAuthEffect
,
useForm
}
from
'
hooks
'
;
import
{
ScrollView
}
from
'
react-native-gesture-handler
'
;
import
{
BigButton
,
Toast
}
from
'
components/core
'
;
import
{
TextField
}
from
'
components/form
'
;
import
{
fieldValidation
,
initialValues
,
textField
}
from
'
./schema
'
;
import
{
generateValidationSchema
}
from
'
utils/form
'
;
import
{
TextField
}
from
'
components/form
'
;
import
{
ScrollView
}
from
'
react-native-gesture-handler
'
;
import
{
UserContext
}
from
'
provider
'
;
import
{
layoutStyles
}
from
'
styles
'
;
import
{
BigButton
}
from
'
components/core
'
;
import
{
GoogleLoginButton
}
from
'
../components
'
;
import
{
Section
}
from
'
components/layout
'
;
const
isPasswordField
=
(
name
:
string
)
=>
name
===
'
password1
'
||
name
===
'
password2
'
;
const
ManualRegistrationPage
:
FC
=
()
=>
{
const
{
getTextInputProps
,
handleSubmit
,
isSubmitting
}
=
useForm
({
const
{
signup
,
loginWithGoogle
,
isLoading
}
=
useContext
(
UserContext
);
const
{
getTextInputProps
,
handleSubmit
,
isSubmitting
,
setFieldError
,
}
=
useForm
({
initialValues
,
validationSchema
:
generateValidationSchema
(
fieldValidation
),
onSubmit
:
async
(
values
)
=>
{
console
.
log
(
values
);
const
response
=
await
signup
(
values
);
if
(
!
response
.
success
)
{
setFieldError
(
'
name
'
,
response
.
error
.
name
);
setFieldError
(
'
email
'
,
response
.
error
.
email
);
setFieldError
(
'
password1
'
,
response
.
error
.
password1
);
setFieldError
(
'
password2
'
,
response
.
error
.
password2
);
Toast
.
show
({
type
:
'
error
'
,
text1
:
'
Gagal registrasi akun
'
,
text2
:
'
Terjadi kesalahan registrasi. Silakan coba lagi
'
,
});
}
},
});
useAuthEffect
();
return
(
<
ScrollView
contentContainerStyle
=
{
layoutStyles
}
>
{
textField
.
map
((
fieldProps
,
i
)
=>
{
return
(
<
TextField
key
=
{
`field
${
i
}
`
}
label
=
{
fieldProps
.
label
}
required
=
{
fieldProps
.
required
}
placeholder
=
{
fieldProps
.
placeholder
}
{
...
getTextInputProps
(
fieldProps
.
name
)
}
secureTextEntry
=
{
isPasswordField
(
fieldProps
.
name
)
}
/>
);
})
}
{
textField
.
map
((
fieldProps
,
i
)
=>
(
<
TextField
key
=
{
`field
${
i
}
`
}
label
=
{
fieldProps
.
label
}
required
=
{
fieldProps
.
required
}
placeholder
=
{
fieldProps
.
placeholder
}
{
...
getTextInputProps
(
fieldProps
.
name
)
}
secureTextEntry
=
{
isPasswordField
(
fieldProps
.
name
)
}
/>
))
}
<
BigButton
title
=
"daftarkan akun"
onPress
=
{
handleSubmit
}
loading
=
{
isSubmitting
}
testID
=
"submitButton"
/>
<
Section
>
<
GoogleLoginButton
onPress
=
{
loginWithGoogle
}
isLoading
=
{
isLoading
}
/>
</
Section
>
</
ScrollView
>
);
};
...
...
src/scenes/
common
/ManualRegistrationPage/schema.ts
→
src/scenes/
auth
/ManualRegistrationPage/schema.ts
View file @
b87e5675
import
{
RegistrationRequest
}
from
'
services/auth/models
'
;
import
{
TextFieldSchema
}
from
'
types/form
'
;
import
{
FieldType
,
FieldValidation
}
from
'
utils/form
'
;
...
...
@@ -28,7 +29,7 @@ export const textField: TextFieldSchema[] = [
},
];
export
const
initialValues
=
{
export
const
initialValues
:
RegistrationRequest
=
{
name
:
''
,
email
:
''
,
password1
:
''
,
...
...
src/scenes/auth/components/GoogleLoginButton/index.tsx
View file @
b87e5675
...
...
@@ -10,10 +10,10 @@ const GoogleLoginButton: FC<Props> = ({ onPress, isLoading }) => (
title
=
"Lanjut dengan Google"
icon
=
{
<
Image
source
=
{
googleLogo
}
style
=
{
styles
.
googleIcon
}
/>
}
onPress
=
{
onPress
}
containerStyle
=
{
styles
.
container
}
buttonStyle
=
{
styles
.
button
}
titleStyle
=
{
styles
.
title
}
disabled
=
{
isLoading
}
raised
/>
);
...
...
src/scenes/auth/components/GoogleLoginButton/styles.ts
View file @
b87e5675
import
{
StyleSheet
}
from
'
react-native
'
;
import
{
typography
}
from
'
styles
'
;
import
{
colors
,
typography
}
from
'
styles
'
;
export
const
styles
=
StyleSheet
.
create
({
googleIcon
:
{
...
...
@@ -8,10 +8,15 @@ export const styles = StyleSheet.create({
},
button
:
{
backgroundColor
:
'
white
'
,
borderColor
:
colors
.
border
,
borderWidth
:
0.5
,
},
container
:
{
elevation
:
1
,
},
title
:
{
paddingLeft
:
24
,
color
:
'
black
'
,
...
typography
.
body
Large
,
...
typography
.
body
Medium
,
},
});
src/scenes/common/InitialPage/index.test.tsx
View file @
b87e5675
...
...
@@ -40,4 +40,15 @@ describe('InitialPage', () => {
ROUTES
.
allAccessQuestionnaire
,
);
});
test
(
'
has link button that navigates to Login Page
'
,
()
=>
{
let
props
=
createTestProps
({});
const
{
getByText
}
=
render
(<
InitialPage
{
...
props
}
/>);
const
link
=
getByText
(
/Login disini/i
);
fireEvent
.
press
(
link
);
expect
(
link
).
toBeTruthy
();
expect
(
props
.
navigation
.
navigate
).
toHaveBeenCalledWith
(
ROUTES
.
login
);
});
});
src/scenes/common/InitialPage/index.tsx
View file @
b87e5675
import
React
,
{
FC
}
from
'
react
'
;
import
{
View
,
Text
,
ImageBackground
,
Image
}
from
'
react-native
'
;
import
{
layoutStyles
,
typographyStyles
}
from
'
styles
'
;
import
{
BigButton
}
from
'
components/core
'
;
import
{
BigButton
,
Link
}
from
'
components/core
'
;
import
*
as
ROUTES
from
'
constants/routes
'
;
import
{
layoutStyles
,
typographyStyles
}
from
'
styles
'
;
import
{
styles
}
from
'
./styles
'
;
import
{
banner_girl_eating
,
logo_white_small
}
from
'
assets/images
'
;
import
*
as
ROUTES
from
'
constants/routes
'
;
const
InitialPage
:
FC
=
({
navigation
})
=>
(
<
ImageBackground
...
...
@@ -31,6 +34,10 @@ const InitialPage: FC = ({ navigation }) => (
title
=
"konsultasi sekarang"
onPress
=
{
()
=>
navigation
.
navigate
(
ROUTES
.
allAccessQuestionnaire
)
}
/>
<
Link
title
=
"Sudah punya akun? Login disini"
onPress
=
{
()
=>
navigation
.
navigate
(
ROUTES
.
login
)
}
/>
</
View
>
</
View
>
</
ImageBackground
>
...
...
src/scenes/common/ManualRegistrationPage/index.test.tsx
deleted
100644 → 0
View file @
717e5abe
import
React
from
'
react
'
;
import
{
render
,
fireEvent
}
from
'
utils/testing
'
;
import
*
as
ROUTES
from
'
constants/routes
'
;
import
ManualRegistrationPage
from
'
.
'
;
import
{
textField
}
from
'
./schema
'
;
import
{
validRegistrationValues
}
from
'
__mocks__/auth
'
;
describe
(
'
ManualRegistrationPage
'
,
()
=>
{
it
(
'
renders correctly
'
,
()
=>
{
render
(<
ManualRegistrationPage
/>,
ROUTES
.
registration
);
});
it
(
'
success when all field is valid
'
,
async
()
=>
{
const
{
getByPlaceholderText
,
getByTestId
}
=
render
(
<
ManualRegistrationPage
/>,
ROUTES
.
registration
,
);
textField
.
map
(({
name
,
placeholder
})
=>
{
const
formField
=
getByPlaceholderText
(
placeholder
as
string
);
fireEvent
.
changeText
(
formField
,
validRegistrationValues
[
name
]);
});
const
submitButton
=
getByTestId
(
'
submitButton
'
);
fireEvent
.
press
(
submitButton
);
});
});
src/scenes/index.ts
View file @
b87e5675
export
{
default
as
LoginPage
}
from
'
./auth/Login
'
;
export
{
default
as
ManualRegistrationPage
}
from
'
./auth/ManualRegistrationPage
'
;
export
{
default
as
InitialPage
}
from
'
./common/InitialPage
'
;
export
{
default
as
ComingSoonPage
}
from
'
./common/ComingSoonPage
'
;
export
{
default
as
ManualRegistrationPage
}
from
'
./common/ManualRegistrationPage
'
;
export
{
default
as
AllAccessQuestionnaire
}
from
'
./questionnaire/AllAccessQuestionnaire
'
;
export
{
default
as
DietelaQuizResult
}
from
'
./questionnaire/DietelaQuizResult
'
;
...
...
src/services/auth/index.ts
View file @
b87e5675
import
{
api
,
RequestMethod
,
ApiResponse
}
from
'
../api
'
;
import
*
as
apiUrls
from
'
./urls
'
;
import
{
GoogleLoginRequest
,
GoogleLoginResponse
}
from
'
./models
'
;
import
{
GoogleLoginRequest
,
LoginResponse
,
RegistrationRequest
,
}
from
'
./models
'
;
export
const
googleLoginApi
=
(
body
:
GoogleLoginRequest
,
):
ApiResponse
<
Google
LoginResponse
>
=>
{
):
ApiResponse
<
LoginResponse
>
=>
{
return
api
(
RequestMethod
.
POST
,
apiUrls
.
google
,
body
);
};
export
const
signupApi
=
(
body
:
RegistrationRequest
,
):
ApiResponse
<
LoginResponse
>
=>
{
return
api
(
RequestMethod
.
POST
,
apiUrls
.
signup
,
body
);
};
src/services/auth/models.ts
View file @
b87e5675
...
...
@@ -2,13 +2,20 @@ export interface GoogleLoginRequest {
access_token
:
string
;
}
export
interface
RegistrationRequest
{
name
:
string
;
email
:
string
;
password1
:
string
;
password2
:
string
;
}
export
interface
User
{
id
:
number
|
null
;
email
:
string
;
name
:
string
;
}
export
interface
Google
LoginResponse
{
export
interface
LoginResponse
{
access_token
:
string
;
refresh_token
:
string
;
user
:
User
;
...
...
Prev
1
2
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment