Fakultas Ilmu Komputer UI
Skip to content
GitLab
Menu
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
8dd444bf
Commit
8dd444bf
authored
May 10, 2021
by
Doan Andreas Nathanael
Browse files
Nutritionist & Admin Login UI & API Integration
parent
7c265074
Changes
9
Hide whitespace changes
Inline
Side-by-side
src/constants/navigation.ts
View file @
8dd444bf
...
...
@@ -12,6 +12,7 @@ import {
NutritionistDetail
,
PaymentResult
,
ExtendedQuestionnaire
,
NutritionistAdminLogin
,
}
from
'
scenes
'
;
import
{
FC
}
from
'
react
'
;
import
{
...
...
@@ -73,6 +74,11 @@ export const publicNavigation: NavRoute[] = [
component
:
LoginPage
,
header
:
'
Login
'
,
},
{
name
:
ROUTES
.
nutritionistAdminLogin
,
component
:
NutritionistAdminLogin
,
header
:
'
Login Tim Dietela
'
,
},
];
export
const
unpaidClientNavigation
:
NavRoute
[]
=
[
...
...
@@ -152,6 +158,11 @@ export const testNavigation: NavRoute[] = [
component
:
LoginPage
,
header
:
'
Login
'
,
},
{
name
:
ROUTES
.
nutritionistAdminLogin
,
component
:
NutritionistAdminLogin
,
header
:
'
Login Tim Dietela
'
,
},
{
name
:
ROUTES
.
checkout
,
component
:
Checkout
,
...
...
src/constants/routes.ts
View file @
8dd444bf
...
...
@@ -16,6 +16,7 @@ export const nutritionistDetail = `${checkout}/nutritionist`;
export
const
registration
=
'
registration
'
;
export
const
login
=
'
login
'
;
export
const
nutritionistAdminLogin
=
'
nutritionist-admin-login
'
;
const
profile
=
'
profile
'
;
export
const
clientProfile
=
`
${
profile
}
/client`
;
...
...
src/scenes/auth/Login/index.test.tsx
View file @
8dd444bf
...
...
@@ -72,4 +72,18 @@ describe('Login page', () => {
afterAll
(()
=>
{
jest
.
clearAllMocks
();
});
test
(
'
has link button that navigates to Nutritionist/Admin Login Page
'
,
async
()
=>
{
const
{
getByText
,
queryByText
,
queryAllByText
}
=
render
(
<
Login
/>,
ROUTES
.
login
,
);
expect
(
queryByText
(
/Login sebagai Nutrisionis/i
)).
toBeTruthy
();
await
waitFor
(()
=>
fireEvent
.
press
(
getByText
(
/Login sebagai Nutrisionis/i
)),
);
expect
(
queryAllByText
(
/Login Tim Dietela/i
)).
toBeTruthy
();
});
});
src/scenes/auth/Login/index.tsx
View file @
8dd444bf
...
...
@@ -4,19 +4,22 @@ import { UserContext } from 'provider';
import
{
useForm
}
from
'
hooks
'
;
import
{
GoogleLoginButton
}
from
'
../components
'
;
import
{
BigButton
,
Toast
}
from
'
components/core
'
;
import
{
BigButton
,
Link
,
Toast
}
from
'
components/core
'
;
import
{
fieldValidation
,
initialValues
,
textField
}
from
'
./schema
'
;
import
{
generateValidationSchema
}
from
'
utils/form
'
;
import
{
layoutStyles
}
from
'
styles
'
;
import
{
TextField
}
from
'
components/form
'
;
import
{
Section
}
from
'
components/layout
'
;
import
{
useNavigation
}
from
'
@react-navigation/core
'
;
import
*
as
ROUTES
from
'
constants/routes
'
;
const
isPasswordField
=
(
name
:
string
)
=>
name
===
'
password
'
;
const
Login
:
FC
=
()
=>
{
const
{
login
,
isLoading
,
loginWithGoogle
}
=
useContext
(
UserContext
);
const
[
nonFieldError
,
setNonFieldError
]
=
useState
<
string
|
null
>
();
const
navigation
=
useNavigation
();
const
{
getTextInputProps
,
...
...
@@ -71,6 +74,12 @@ const Login: FC = () => {
<
Section
>
<
GoogleLoginButton
onPress
=
{
loginWithGoogle
}
isLoading
=
{
isLoading
}
/>
</
Section
>
<
Section
>
<
Link
title
=
"Login sebagai Nutrisionis/Admin"
onPress
=
{
()
=>
navigation
.
navigate
(
ROUTES
.
nutritionistAdminLogin
)
}
/>
</
Section
>
</
View
>
);
};
...
...
src/scenes/auth/Login/schema.ts
View file @
8dd444bf
import
{
LoginRequest
,
Role
}
from
'
services/auth/models
'
;
import
{
LoginRequest
,
User
Role
}
from
'
services/auth/models
'
;
import
{
TextFieldSchema
}
from
'
types/form
'
;
import
{
FieldType
,
FieldValidation
}
from
'
utils/form
'
;
...
...
@@ -20,7 +20,7 @@ export const textField: TextFieldSchema[] = [
export
const
initialValues
:
LoginRequest
=
{
email
:
''
,
password
:
''
,
role
:
'
client
'
,
role
:
UserRole
.
CLIENT
,
};
export
const
fieldValidation
:
FieldValidation
[]
=
[
...
...
@@ -38,4 +38,4 @@ export const fieldValidation: FieldValidation[] = [
},
];
export
const
setRole
=
(
role
:
Role
)
=>
(
initialValues
.
role
=
role
);
export
const
setRole
=
(
role
:
User
Role
)
=>
(
initialValues
.
role
=
role
);
src/scenes/auth/NutritionistAdminLogin/index.test.tsx
0 → 100644
View file @
8dd444bf
import
React
from
'
react
'
;
import
{
render
,
fireEvent
,
waitFor
}
from
'
utils/testing
'
;
import
*
as
ROUTES
from
'
constants/routes
'
;
import
axios
from
'
axios
'
;
import
NutritionistAdminLogin
from
'
.
'
;
import
{
authResponse
,
validLoginValues
}
from
'
__mocks__/auth
'
;
import
{
textField
}
from
'
./schema
'
;
jest
.
mock
(
'
react-native-toast-message
'
);
jest
.
mock
(
'
axios
'
);
const
mockAxios
=
axios
as
jest
.
Mocked
<
typeof
axios
>
;
describe
(
'
NutritionistAdminLogin
'
,
()
=>
{
it
(
'
renders correctly
'
,
()
=>
{
render
(<
NutritionistAdminLogin
/>,
ROUTES
.
nutritionistAdminLogin
);
});
it
(
'
success when field is valid and submit success
'
,
async
()
=>
{
const
loginApi
=
()
=>
Promise
.
resolve
({
status
:
201
,
data
:
authResponse
,
});
mockAxios
.
request
.
mockImplementationOnce
(
loginApi
);
const
{
getByPlaceholderText
,
getByTestId
}
=
render
(
<
NutritionistAdminLogin
/>,
ROUTES
.
nutritionistAdminLogin
,
);
textField
.
map
(({
name
,
placeholder
})
=>
{
const
formField
=
getByPlaceholderText
(
placeholder
as
string
);
fireEvent
.
changeText
(
formField
,
validLoginValues
[
name
]);
});
const
loginButton
=
getByTestId
(
'
timDietelaLogin
'
);
await
waitFor
(()
=>
fireEvent
.
press
(
loginButton
));
});
it
(
'
fails when field is valid and submit fails
'
,
async
()
=>
{
const
loginApi
=
()
=>
Promise
.
reject
({
status
:
400
,
response
:
{
data
:
'
error
'
,
},
});
mockAxios
.
request
.
mockImplementationOnce
(
loginApi
);
const
{
getByPlaceholderText
,
queryByText
,
getByTestId
}
=
render
(
<
NutritionistAdminLogin
/>,
ROUTES
.
nutritionistAdminLogin
,
);
textField
.
map
(({
name
,
placeholder
})
=>
{
const
formField
=
getByPlaceholderText
(
placeholder
as
string
);
fireEvent
.
changeText
(
formField
,
validLoginValues
[
name
]);
});
const
loginButton
=
getByTestId
(
'
timDietelaLogin
'
);
await
waitFor
(()
=>
fireEvent
.
press
(
loginButton
));
const
toastWarning
=
queryByText
(
/Profile/i
);
expect
(
toastWarning
).
toBeFalsy
();
});
afterAll
(()
=>
{
jest
.
clearAllMocks
();
});
test
(
'
has link button that navigates to Nutritionist/Admin Login Page
'
,
async
()
=>
{
const
{
getByText
,
queryByText
,
queryAllByText
}
=
render
(
<
NutritionistAdminLogin
/>,
ROUTES
.
nutritionistAdminLogin
,
);
expect
(
queryByText
(
/Login sebagai Pengguna/i
)).
toBeTruthy
();
await
waitFor
(()
=>
fireEvent
.
press
(
getByText
(
/Login sebagai Pengguna/i
)));
expect
(
queryAllByText
(
/Login/i
)).
toBeTruthy
();
});
});
src/scenes/auth/NutritionistAdminLogin/index.tsx
0 → 100644
View file @
8dd444bf
import
React
,
{
FC
,
useContext
,
useState
}
from
'
react
'
;
import
{
ScrollView
,
StyleSheet
,
Text
}
from
'
react-native
'
;
import
{
UserContext
}
from
'
provider
'
;
import
{
useForm
}
from
'
hooks
'
;
import
{
BigButton
,
Link
,
Toast
}
from
'
components/core
'
;
import
{
fieldValidation
,
initialValues
,
radioButtonGroups
,
textField
,
}
from
'
./schema
'
;
import
{
generateValidationSchema
}
from
'
utils/form
'
;
import
{
layoutStyles
}
from
'
styles
'
;
import
{
RadioButtonGroup
,
TextField
}
from
'
components/form
'
;
import
{
Section
}
from
'
components/layout
'
;
import
{
useNavigation
}
from
'
@react-navigation/core
'
;
import
*
as
ROUTES
from
'
constants/routes
'
;
const
isPasswordField
=
(
name
:
string
)
=>
name
===
'
password
'
;
const
NutritionistAdminLogin
:
FC
=
()
=>
{
const
{
login
}
=
useContext
(
UserContext
);
const
[
nonFieldError
,
setNonFieldError
]
=
useState
<
string
|
null
>
();
const
navigation
=
useNavigation
();
const
{
getTextInputProps
,
getFormFieldProps
,
handleSubmit
,
isSubmitting
,
setFieldError
,
}
=
useForm
({
initialValues
,
validationSchema
:
generateValidationSchema
(
fieldValidation
),
onSubmit
:
async
(
values
)
=>
{
const
response
=
await
login
(
values
);
if
(
!
response
.
success
)
{
const
error
=
response
.
error
;
setFieldError
(
'
email
'
,
error
.
email
);
setFieldError
(
'
password
'
,
error
.
password
);
setNonFieldError
(
error
.
non_field_errors
);
Toast
.
show
({
type
:
'
error
'
,
text1
:
'
Gagal login akun
'
,
text2
:
'
Terjadi kesalahan login. Silakan coba lagi
'
,
});
}
},
});
return
(
<
ScrollView
contentContainerStyle
=
{
layoutStyles
}
>
{
textField
.
map
(({
name
,
label
,
required
,
placeholder
},
i
)
=>
(
<
TextField
key
=
{
`field
${
i
}
`
}
label
=
{
label
}
required
=
{
required
}
placeholder
=
{
placeholder
}
{
...
getTextInputProps
(
name
)
}
secureTextEntry
=
{
isPasswordField
(
name
)
}
/>
))
}
{
radioButtonGroups
.
map
((
fieldProps
,
i
)
=>
(
<
RadioButtonGroup
{
...
fieldProps
}
{
...
getFormFieldProps
(
fieldProps
.
name
)
}
key
=
{
`radiobuttongroup
${
i
}
`
}
/>
))
}
{
nonFieldError
&&
(
<
Text
style
=
{
styles
.
nonfieldError
}
>
{
nonFieldError
}
</
Text
>
)
}
<
Section
>
<
BigButton
title
=
"login"
onPress
=
{
handleSubmit
}
loading
=
{
isSubmitting
}
testID
=
"timDietelaLogin"
/>
</
Section
>
<
Section
>
<
Link
title
=
"Login sebagai Pengguna"
onPress
=
{
()
=>
navigation
.
navigate
(
ROUTES
.
login
)
}
/>
</
Section
>
</
ScrollView
>
);
};
const
styles
=
StyleSheet
.
create
({
nonfieldError
:
{
color
:
'
red
'
},
});
export
default
NutritionistAdminLogin
;
src/scenes/auth/NutritionistAdminLogin/schema.ts
0 → 100644
View file @
8dd444bf
import
{
LoginRequest
,
UserRole
}
from
'
services/auth/models
'
;
import
{
RadioButtonGroupSchema
,
TextFieldSchema
}
from
'
types/form
'
;
import
{
FieldType
,
FieldValidation
}
from
'
utils/form
'
;
export
const
textField
:
TextFieldSchema
[]
=
[
{
label
:
'
Email address
'
,
placeholder
:
'
Masukkan email Anda
'
,
required
:
true
,
name
:
'
email
'
,
},
{
label
:
'
Password
'
,
placeholder
:
'
Masukkan password Anda
'
,
required
:
true
,
name
:
'
password
'
,
},
];
export
const
radioButtonGroups
:
RadioButtonGroupSchema
[]
=
[
{
label
:
'
Role
'
,
required
:
true
,
name
:
'
role
'
,
choices
:
[
{
value
:
UserRole
.
NUTRITIONIST
,
label
:
'
Nutrisionis
'
,
},
{
value
:
UserRole
.
ADMIN
,
label
:
'
Admin
'
,
},
],
},
];
export
const
initialValues
:
LoginRequest
=
{
email
:
''
,
password
:
''
,
role
:
UserRole
.
NUTRITIONIST
,
};
export
const
fieldValidation
:
FieldValidation
[]
=
[
{
name
:
'
email
'
,
required
:
true
,
label
:
'
Email address
'
,
type
:
FieldType
.
EMAIL
,
},
{
name
:
'
password
'
,
required
:
true
,
label
:
'
Password
'
,
type
:
FieldType
.
PASSWORD
,
},
];
export
const
setRole
=
(
role
:
UserRole
)
=>
(
initialValues
.
role
=
role
);
src/scenes/index.ts
View file @
8dd444bf
export
{
default
as
LoginPage
}
from
'
./auth/Login
'
;
export
{
default
as
NutritionistAdminLogin
}
from
'
./auth/NutritionistAdminLogin
'
;
export
{
default
as
ManualRegistrationPage
}
from
'
./auth/ManualRegistrationPage
'
;
export
{
default
as
InitialPage
}
from
'
./common/InitialPage
'
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a 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