You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

454 lines
16 KiB

  1. import {
  2. useEffect,
  3. useState,
  4. useRef,
  5. lazy,
  6. useContext
  7. } from 'react';
  8. import { Link as RouterLink } from 'react-router-dom';
  9. import { useNavigate } from 'react-router-dom';
  10. import { useForm, } from 'react-hook-form'
  11. import { getBowserType, isAppBowser, iAmSmartCallbackPath } from 'auth/utils'
  12. import * as HttpUtils from "utils/HttpUtils";
  13. import { I_AM_SMART_PATH } from "utils/ApiPathConst";
  14. // material-ui
  15. import {
  16. Button,
  17. FormHelperText,
  18. Grid,
  19. Link,
  20. IconButton,
  21. InputAdornment,
  22. InputLabel,
  23. OutlinedInput,
  24. Stack,
  25. Typography
  26. } from '@mui/material';
  27. // third party
  28. import * as yup from 'yup';
  29. import { useFormik, FormikProvider } from 'formik';
  30. import AnimateButton from 'components/@extended/AnimateButton';
  31. import Loadable from 'components/Loadable';
  32. const PasswordAlertDialog = Loadable(lazy(() => import('./PasswordAlertDialog')));
  33. // assets
  34. import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
  35. // import axios from "axios";
  36. import { useDispatch } from "react-redux";
  37. import { handleLogin } from "auth/index";
  38. import useJwt from "auth/jwt/useJwt";
  39. import { FormattedMessage, useIntl } from "react-intl";
  40. import { SysContext } from "components/SysSettingProvider"
  41. import { IAmSmartButton } from "components/iAmSmartButton";
  42. import usePageTitle from "components/usePageTitle";
  43. const LoadingComponent = Loadable(lazy(() => import('pages/extra-pages/LoadingComponent')));
  44. // ============================|| FIREBASE - LOGIN ||============================ //
  45. const AuthLoginCustom = () => {
  46. const dispatch = useDispatch()
  47. const navigate = useNavigate()
  48. const intl = useIntl();
  49. const { locale } = intl;
  50. const { sysSetting } = useContext(SysContext);
  51. // Localized document title/meta on login page
  52. usePageTitle("login");
  53. const [showPassword, setShowPassword] = useState(false);
  54. const handleClickShowPassword = () => {
  55. setShowPassword(!showPassword);
  56. };
  57. const [isValid, setisValid] = useState(false);
  58. const [open, setOpen] = useState(false);
  59. const [isButtonDisabled, setIsButtonDisabled] = useState(true);
  60. const [errorMassage, setErrorMassage] = useState('');
  61. const [onLogin, setOnLogin] = useState(false);
  62. /** Blocks a second login before React re-renders (double-click / rapid taps). */
  63. const loginInFlightRef = useRef(false);
  64. const handleMouseDownPassword = (event) => {
  65. event.preventDefault();
  66. };
  67. const tryLogin = () => {
  68. if (isValid) {
  69. if (loginInFlightRef.current) {
  70. return;
  71. }
  72. loginInFlightRef.current = true;
  73. setOnLogin(true)
  74. useJwt
  75. .login({ username: values.username, password: values.password })
  76. .then((response) => {
  77. // console.log(response)
  78. const userData = {
  79. id: response.data.id,
  80. fullenName: response.data.name,
  81. fullchName: response.data.chName,
  82. email: response.data.email,
  83. type: response.data.type,
  84. role: response.data.role,
  85. abilities: response.data.abilities,
  86. creditor: response.data.creditor,
  87. locale: response.data.preferLocale,
  88. passwordExpiryDate: response.data.passwordExpiryDate,
  89. orgPaymentRecord: response.data.orgPaymentRecord,
  90. orgDnRecord: response.data.orgDnRecord,
  91. //avatar: require('src/assets/images/users/avatar-3.png').default,
  92. }
  93. const data = { ...userData, accessToken: response.data.accessToken, refreshToken: response.data.refreshToken }
  94. // setSuccess(true)
  95. // console.log(response.data);
  96. if (response.data.type === "GLD") {
  97. // setLocale("en");
  98. localStorage.setItem('locale', 'en');
  99. } else {
  100. if (response.data.preferLocale === "zh_HK") {
  101. // setLocale("zh-HK");
  102. localStorage.setItem('locale', 'zh-HK');
  103. }
  104. if (response.data.preferLocale === "zh-CN") {
  105. // setLocale("zh-CN");
  106. localStorage.setItem('locale', 'zh-CN');
  107. }
  108. if (response.data.preferLocale === "en") {
  109. // setLocale("zh-CN");
  110. localStorage.setItem('locale', 'en');
  111. }
  112. }
  113. dispatch(handleLogin(data))
  114. navigate('/dashboard');
  115. location.reload()
  116. // setSumitting(false)
  117. })
  118. .catch((error) => {
  119. // console.log(error.response)
  120. // setSuccess(false)
  121. loginInFlightRef.current = false;
  122. setOnLogin(false)
  123. if (error.response != undefined) {
  124. setErrorMassage(error.response.data.error)
  125. } else {
  126. setErrorMassage("CONNECTION_ERROR")
  127. }
  128. setOpen(true)
  129. });
  130. } else {
  131. setOpen(true)
  132. }
  133. }
  134. const formik = useFormik({
  135. initialValues: ({
  136. username: '',
  137. password: '',
  138. submit: null
  139. }),
  140. validationSchema: yup.object().shape({
  141. username: yup.string().required(intl.formatMessage({ id: 'requireUsername' })),
  142. password: yup.string().min(8, intl.formatMessage({ id: 'atLeast8CharPassword' })).required(intl.formatMessage({ id: 'requirePassword' }))
  143. .matches(/^(?=.*[a-z])/, intl.formatMessage({ id: 'atLeastOneSmallLetter' }))
  144. .matches(/^(?=.*[A-Z])/, intl.formatMessage({ id: 'atLeastOneCapLetter' }))
  145. .matches(/^(?=.*[0-9])/, intl.formatMessage({ id: 'atLeast1Number' }))
  146. .matches(/^(?=.*\W)/, intl.formatMessage({ id: 'atLeast1SpecialChar' })),
  147. }),
  148. });
  149. const checkDataField = (data) => {
  150. if (data.username !== "" &&
  151. data.password !== "" &&
  152. handlePassword(data.password)
  153. // &&handle6Digi(data.username)
  154. ) {
  155. setisValid(true)
  156. setIsButtonDisabled(false);
  157. return isValid
  158. } else {
  159. setisValid(false)
  160. setIsButtonDisabled(true);
  161. return isValid
  162. }
  163. };
  164. function handlePassword(password) {
  165. let new_pass = password;
  166. // regular expressions to validate password
  167. var lowerCase = /[a-z]/g;
  168. var upperCase = /[A-Z]/g;
  169. var numbers = /[0-9]/g;
  170. var symbol = /^(?=.*\W)/;
  171. if (!new_pass.match(lowerCase)) {
  172. return false;
  173. } else if (!new_pass.match(upperCase)) {
  174. return false;
  175. } else if (!new_pass.match(numbers)) {
  176. return false;
  177. } else if (!new_pass.match(symbol)) {
  178. return false;
  179. } else if (new_pass.length < 8) {
  180. return false;
  181. } else {
  182. return true;
  183. }
  184. }
  185. const handleClose = () => {
  186. setOpen(false);
  187. };
  188. const { values } = formik
  189. useEffect(() => {
  190. checkDataField(values)
  191. }, [values])
  192. const { handleSubmit } = useForm({})
  193. function getQRWithIAmSmart() {
  194. if (isAppBowser()) {
  195. openApp();
  196. } else {
  197. openQR();
  198. }
  199. }
  200. const openQR = () => {
  201. let callbackUrl = "https://" + iAmSmartCallbackPath() + "/iamsmart/authcallback";
  202. HttpUtils.get({
  203. url: I_AM_SMART_PATH,
  204. onSuccess: (responseData) => {
  205. let url = responseData.iAmSmartPath + "/api/v1/auth/getQR"
  206. + "?clientID=" + responseData.ci
  207. + "&responseType=code"
  208. + "&source=" + getBowserType()
  209. + "&redirectURI=" + encodeURIComponent(callbackUrl)
  210. + "&scope=" + encodeURIComponent("eidapi_auth eidapi_profiles")
  211. + "&lang=" + (locale === 'en' ? "en-US" : locale === 'zh-HK' ? "zh-HK" : "zh-CN")
  212. //+"&state="
  213. + "&brokerPage=true"
  214. window.location = url;
  215. }
  216. });
  217. }
  218. const openApp = () => {
  219. // setTimeout(function () {
  220. // openQR();
  221. // }, 1000);
  222. // let callbackUrl = "https://" + iAmSmartCallbackPath() + "/iamsmart/authcallback";
  223. // let source = getBowserType()
  224. // console.log(source)
  225. // let url = iAmSmartAppPath + "auth"
  226. // + "?clientID=" + clientId
  227. // + "&responseType=code"
  228. // + "&source=" + getBowserType()
  229. // + "&redirectURI=" + encodeURIComponent(callbackUrl)
  230. // + "&scope=" + encodeURIComponent("eidapi_auth eidapi_profiles")
  231. // + "&lang=zh-HK"//en-US, zh-HK, or zh-CN
  232. // //+"&state="
  233. // + "&brokerPage=true"
  234. // window.location=url;
  235. let callbackUrl = "https://" + iAmSmartCallbackPath() + "/iamsmart/authcallback";
  236. HttpUtils.get({
  237. url: I_AM_SMART_PATH,
  238. onSuccess: (responseData) => {
  239. let url = responseData.iAmSmartPath + "/api/v1/auth/getQR"
  240. + "?clientID=" + responseData.ci
  241. + "&responseType=code"
  242. + "&source=" + getBowserType()
  243. + "&redirectURI=" + encodeURIComponent(callbackUrl)
  244. + "&scope=" + encodeURIComponent("eidapi_auth eidapi_profiles")
  245. + "&lang=" + (locale === 'en' ? "en-US" : locale === 'zh-HK' ? "zh-HK" : "zh-CN")
  246. //+"&state="
  247. + "&brokerPage=true"
  248. window.location = url;
  249. }
  250. });
  251. }
  252. return (
  253. <FormikProvider value={formik}>
  254. <form onSubmit={handleSubmit(tryLogin)}>
  255. <Grid container spacing={3}>
  256. <Grid item xs={12}>
  257. <Stack spacing={1}>
  258. <InputLabel htmlFor="email-login">
  259. <Typography variant="h5">
  260. <FormattedMessage id="userLoginName" />
  261. </Typography>
  262. </InputLabel>
  263. <OutlinedInput
  264. id="username"
  265. name="username"
  266. onChange={formik.handleChange}
  267. placeholder=""
  268. fullWidth
  269. autoFocus
  270. value={formik.values.username}
  271. error={Boolean(formik.touched.username && formik.errors.username)}
  272. onBlur={formik.handleBlur}
  273. inputProps={{
  274. "aria-describedby": 'standard-weight-helper-text-username-login',
  275. maxLength: 50,
  276. onKeyDown: (e) => {
  277. if (e.key === 'Enter') {
  278. e.preventDefault();
  279. }
  280. },
  281. }}
  282. />
  283. {formik.touched.username && formik.errors.username && (
  284. <FormHelperText error id="standard-weight-helper-text-username-login">
  285. {formik.errors.username}
  286. </FormHelperText>
  287. )}
  288. </Stack>
  289. </Grid>
  290. <Grid item xs={12}>
  291. <Stack spacing={1}>
  292. <InputLabel htmlFor="password-login"><Typography variant="h5">
  293. <FormattedMessage id="userPassword" />
  294. </Typography></InputLabel>
  295. <OutlinedInput
  296. fullWidth
  297. id="password-login"
  298. type={showPassword ? 'text' : 'password'}
  299. name="password"
  300. value={formik.values.password}
  301. onChange={formik.handleChange}
  302. onBlur={formik.handleBlur}
  303. error={Boolean(formik.touched.password && formik.errors.password)}
  304. endAdornment={
  305. <InputAdornment position="end">
  306. <IconButton
  307. aria-label={intl.formatMessage({
  308. id: showPassword ? "ariaHidePassword" : "ariaShowPassword"
  309. })}
  310. onClick={handleClickShowPassword}
  311. onMouseDown={handleMouseDownPassword}
  312. edge="end"
  313. size="large"
  314. >
  315. {showPassword ? <EyeOutlined /> : <EyeInvisibleOutlined />}
  316. </IconButton>
  317. </InputAdornment>
  318. }
  319. placeholder=""
  320. inputProps={{
  321. "aria-describedby": 'standard-weight-helper-text-password-login',
  322. }}
  323. />
  324. {formik.touched.password && formik.errors.password && (
  325. <FormHelperText error id="standard-weight-helper-text-password-login">
  326. {formik.errors.password}
  327. </FormHelperText>
  328. )}
  329. </Stack>
  330. </Grid>
  331. <Grid item xs={12}>
  332. <Grid container>
  333. <Grid item xs={12}>
  334. <AnimateButton>
  335. {onLogin ?
  336. <LoadingComponent disableText={true} alignItems="center" />
  337. :
  338. <Button disableElevation disabled={isButtonDisabled}
  339. fullWidth size="large" type="submit" variant="contained" color="primary"
  340. sx={{
  341. backgroundColor: "#0C489E",
  342. color: "#FFFFFF",
  343. "&:hover": { backgroundColor: "#093A7A" },
  344. "&.Mui-disabled": {
  345. background: "#bbdefb",
  346. color: "#fff",
  347. border: "2px solid",
  348. borderColor: "#e7e7e7"
  349. }
  350. }}>
  351. <Typography variant="h5">
  352. <FormattedMessage id="login" />
  353. </Typography>
  354. </Button>
  355. }
  356. </AnimateButton>
  357. </Grid>
  358. <Grid item xs={12}>
  359. <Stack direction="row" justifyContent="flex-start" alignItems="center" spacing={1}>
  360. <Link component={RouterLink} to="/forgot/password" color="primary" sx={{ textDecoration: "none" }}>
  361. <Typography align="center" variant="h7">
  362. <FormattedMessage id="forgotUserPassword" />?
  363. </Typography>
  364. </Link>
  365. <Typography align="center" variant="h7">
  366. |
  367. </Typography>
  368. <Link component={RouterLink} to="/forgot/username" color="primary" sx={{ textDecoration: "none" }}>
  369. <Typography align="center" variant="h7">
  370. <FormattedMessage id="forgotUsername" />?
  371. </Typography>
  372. </Link>
  373. </Stack>
  374. </Grid>
  375. </Grid>
  376. </Grid>
  377. {
  378. sysSetting?.allowRegistration ?
  379. <>
  380. <Grid item xs={12} >
  381. <Grid container>
  382. <Grid item xs={12}>
  383. <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
  384. <IAmSmartButton
  385. label={intl.formatMessage({ id: "iAmSmartLogin" })}
  386. onClickFun={getQRWithIAmSmart}
  387. fullWidth={true}
  388. />
  389. </Stack>
  390. </Grid>
  391. <Grid item xs={12}>
  392. <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
  393. <Link href={intl.formatMessage({ id: "iamsmartLink" })} color="primary" sx={{ textDecoration: "none" }}>
  394. <Typography align="center" variant="h7">
  395. {intl.formatMessage({ id: 'learnMore' }) + " >"}
  396. </Typography>
  397. </Link>
  398. </Stack>
  399. </Grid>
  400. </Grid>
  401. </Grid>
  402. <Grid item xs={12}>
  403. <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
  404. <Button fullWidth size="large" variant="outlined" href="/register" ><Typography variant="h5">
  405. <FormattedMessage id="createOrReActivate" />
  406. </Typography></Button>
  407. </Stack>
  408. </Grid>
  409. </>
  410. :
  411. <></>
  412. }
  413. </Grid>
  414. <PasswordAlertDialog open={open} handleClose={handleClose} errorMassage={errorMassage} />
  415. </form>
  416. </FormikProvider>
  417. );
  418. };
  419. export default AuthLoginCustom;