選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

344 行
12 KiB

  1. import React, {
  2. useEffect,
  3. useState,
  4. lazy
  5. } from 'react';
  6. import { Link as RouterLink } from 'react-router-dom';
  7. import { useNavigate } from 'react-router-dom';
  8. import { useForm, } from 'react-hook-form'
  9. import { iAmSmartPath, iAmSmartAppPath, clientId, getBowserType, isAppBowser, iAmSmartCallbackPath } from 'auth/utils'
  10. // material-ui
  11. import {
  12. Button,
  13. //Checkbox,
  14. //Divider,
  15. //FormControlLabel,
  16. FormHelperText,
  17. Grid,
  18. Link,
  19. IconButton,
  20. InputAdornment,
  21. InputLabel,
  22. OutlinedInput,
  23. Stack,
  24. Typography
  25. } from '@mui/material';
  26. // third party
  27. import * as yup from 'yup';
  28. import { useFormik, FormikProvider } from 'formik';
  29. // project import
  30. //import FirebaseSocial from './FirebaseSocial';
  31. import AnimateButton from 'components/@extended/AnimateButton';
  32. import Loadable from 'components/Loadable';
  33. const PasswordAlertDialog = Loadable(lazy(() => import('./PasswordAlertDialog')));
  34. // assets
  35. import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
  36. // import axios from "axios";
  37. import iAmSmartICon from 'assets/images/icons/icon_iAmSmart.png';
  38. import { useDispatch } from "react-redux";
  39. import { handleLogin } from "auth/index";
  40. import useJwt from "../../../auth/jwt/useJwt";
  41. import { handleLogoutFunction } from 'auth/index';
  42. import {FormattedMessage, useIntl} from "react-intl";
  43. // ============================|| FIREBASE - LOGIN ||============================ //
  44. const AuthLoginCustom = () => {
  45. const dispatch = useDispatch()
  46. const navigate = useNavigate()
  47. const intl = useIntl();
  48. const [showPassword, setShowPassword] = useState(false);
  49. const handleClickShowPassword = () => {
  50. setShowPassword(!showPassword);
  51. };
  52. // let [posts, setPosts] = useState([]);
  53. const [isValid, setisValid] = useState(false);
  54. const [open, setOpen] = React.useState(false);
  55. const [isButtonDisabled, setIsButtonDisabled] = useState(true);
  56. const [errorMassage, setErrorMassage] = useState('');
  57. const handleMouseDownPassword = (event) => {
  58. event.preventDefault();
  59. };
  60. const tryLogin = () => {
  61. if (isValid) {
  62. dispatch(handleLogoutFunction());
  63. // setSumitting(true)
  64. useJwt
  65. .login({ username: values.username, password: values.password })
  66. .then((response) => {
  67. console.log(response)
  68. const userData = {
  69. id: response.data.id,
  70. fullenName: response.data.name,
  71. fullchName: response.data.chName,
  72. email: response.data.email,
  73. type: response.data.type,
  74. role: response.data.role,
  75. abilities: response.data.abilities,
  76. creditor: response.data.creditor,
  77. //avatar: require('src/assets/images/users/avatar-3.png').default,
  78. }
  79. const data = { ...userData, accessToken: response.data.accessToken, refreshToken: response.data.refreshToken }
  80. // setSuccess(true)
  81. dispatch(handleLogin(data))
  82. navigate('/dashboard');
  83. location.reload()
  84. // setSumitting(false)
  85. })
  86. .catch((error) => {
  87. // setSuccess(false)
  88. console.error(error)
  89. console.error(error.response.data.error)
  90. setErrorMassage(error.response.data.error)
  91. setOpen(true)
  92. });
  93. } else {
  94. setOpen(true)
  95. }
  96. }
  97. const formik = useFormik({
  98. initialValues: ({
  99. username: '',
  100. password: '',
  101. submit: null
  102. }),
  103. validationSchema: yup.object().shape({
  104. // username: yup.string().min(6,'用戶名稱最少6位').required('請輸入用戶名稱'),
  105. username: yup.string().required(intl.formatMessage({id: 'requireUsername'})),
  106. password: yup.string().min(8, intl.formatMessage({id: 'atLeast8CharPassword'})).required(intl.formatMessage({id: 'requirePassword'}))
  107. .matches(/^(?=.*[a-z])/, intl.formatMessage({id: 'atLeastOneSmallLetter'}))
  108. .matches(/^(?=.*[A-Z])/, intl.formatMessage({id: 'atLeastOneCapLetter'}))
  109. .matches(/^(?=.*[0-9])/, intl.formatMessage({id: 'atLeast1Number'}))
  110. .matches(/^(?=.*[!@#%&])/, intl.formatMessage({id: 'atLeast1SpecialChar'})),
  111. }),
  112. });
  113. const checkDataField = (data) => {
  114. if (data.username !== "" &&
  115. data.password !== "" &&
  116. handlePassword(data.password)
  117. // &&handle6Digi(data.username)
  118. ) {
  119. setisValid(true)
  120. setIsButtonDisabled(false);
  121. return isValid
  122. } else {
  123. setisValid(false)
  124. setIsButtonDisabled(true);
  125. return isValid
  126. }
  127. };
  128. function handlePassword(password) {
  129. let new_pass = password;
  130. // regular expressions to validate password
  131. var lowerCase = /[a-z]/g;
  132. var upperCase = /[A-Z]/g;
  133. var numbers = /[0-9]/g;
  134. var symbol = /^(?=.*[!@#%&])/;
  135. if (!new_pass.match(lowerCase)) {
  136. return false;
  137. } else if (!new_pass.match(upperCase)) {
  138. return false;
  139. } else if (!new_pass.match(numbers)) {
  140. return false;
  141. } else if (!new_pass.match(symbol)) {
  142. return false;
  143. } else if (new_pass.length < 8) {
  144. return false;
  145. } else {
  146. return true;
  147. }
  148. }
  149. const handleClose = () => {
  150. setOpen(false);
  151. };
  152. const { values } = formik
  153. useEffect(() => {
  154. checkDataField(values)
  155. }, [values])
  156. const { handleSubmit } = useForm({})
  157. const getQRWithIAmSmart = () => {
  158. if (isAppBowser()) {
  159. openApp();
  160. } else {
  161. openQR();
  162. }
  163. }
  164. const openQR = () => {
  165. let callbackUrl = "https://" + iAmSmartCallbackPath() + "/iamsmart/authcallback";
  166. let url = iAmSmartPath + "/api/v1/auth/getQR"
  167. + "?clientID=" + clientId
  168. + "&responseType=code"
  169. + "&source=" + getBowserType()
  170. + "&redirectURI=" + encodeURIComponent(callbackUrl)
  171. + "&scope=" + encodeURIComponent("eidapi_auth eidapi_profiles")
  172. + "&lang=zh-HK"//en-US, zh-HK, or zh-CN
  173. //+"&state="
  174. + "&brokerPage=false"
  175. window.location=url;
  176. }
  177. const openApp = () => {
  178. // setTimeout(function () {
  179. // openQR();
  180. // }, 1000);
  181. let callbackUrl = "https://" + iAmSmartCallbackPath() + "/iamsmart/authcallback";
  182. let url = iAmSmartAppPath + "auth"
  183. + "?clientID=" + clientId
  184. + "&responseType=code"
  185. + "&source=" + getBowserType()
  186. + "&redirectURI=" + encodeURIComponent(callbackUrl)
  187. + "&scope=" + encodeURIComponent("eidapi_auth eidapi_profiles")
  188. + "&lang=zh-HK"//en-US, zh-HK, or zh-CN
  189. //+"&state="
  190. + "&brokerPage=false"
  191. window.location=url;
  192. }
  193. return (
  194. <FormikProvider value={formik}>
  195. <form onSubmit={handleSubmit(tryLogin)}>
  196. <Grid container spacing={3}>
  197. <Grid item xs={12}>
  198. <Stack spacing={1}>
  199. <InputLabel htmlFor="email-login">
  200. <Typography variant="h5">
  201. <FormattedMessage id="userLoginName"/>
  202. </Typography>
  203. </InputLabel>
  204. <OutlinedInput
  205. id="username"
  206. name="username"
  207. onChange={formik.handleChange}
  208. placeholder=""
  209. fullWidth
  210. value={formik.values.username}
  211. error={Boolean(formik.touched.username && formik.errors.username)}
  212. onBlur={formik.handleBlur}
  213. inputProps={{
  214. maxLength: 50,
  215. onKeyDown: (e) => {
  216. if (e.key === 'Enter') {
  217. e.preventDefault();
  218. }
  219. },
  220. }}
  221. />
  222. {formik.touched.username && formik.errors.username && (
  223. <FormHelperText error id="standard-weight-helper-text-username-login">
  224. {formik.errors.username}
  225. </FormHelperText>
  226. )}
  227. </Stack>
  228. </Grid>
  229. <Grid item xs={12}>
  230. <Stack spacing={1}>
  231. <InputLabel htmlFor="password-login"><Typography variant="h5">
  232. <FormattedMessage id="userPassword"/>
  233. </Typography></InputLabel>
  234. <OutlinedInput
  235. fullWidth
  236. id="password-login"
  237. type={showPassword ? 'text' : 'password'}
  238. name="password"
  239. value={formik.values.password}
  240. onChange={formik.handleChange}
  241. onBlur={formik.handleBlur}
  242. error={Boolean(formik.touched.password && formik.errors.password)}
  243. endAdornment={
  244. <InputAdornment position="end">
  245. <IconButton
  246. aria-label="toggle password visibility"
  247. onClick={handleClickShowPassword}
  248. onMouseDown={handleMouseDownPassword}
  249. edge="end"
  250. size="large"
  251. >
  252. {showPassword ? <EyeOutlined /> : <EyeInvisibleOutlined />}
  253. </IconButton>
  254. </InputAdornment>
  255. }
  256. placeholder=""
  257. />
  258. {formik.touched.password && formik.errors.password && (
  259. <FormHelperText error id="standard-weight-helper-text-password-login">
  260. {formik.errors.password}
  261. </FormHelperText>
  262. )}
  263. </Stack>
  264. </Grid>
  265. <Grid item xs={12}>
  266. <AnimateButton>
  267. <Button disableElevation disabled={isButtonDisabled}
  268. fullWidth size="large" type="submit" variant="contained" color="primary"
  269. sx={{
  270. "&.Mui-disabled": {
  271. background: "#bbdefb",
  272. color: "#fff",
  273. border: "2px solid",
  274. borderColor: "#e7e7e7"
  275. }
  276. }}>
  277. <Typography variant="h5">
  278. <FormattedMessage id="login"/>
  279. </Typography>
  280. </Button>
  281. </AnimateButton>
  282. </Grid>
  283. <Grid item xs={12}>
  284. <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
  285. <Link component={RouterLink} to="" color="primary">
  286. <Typography align="center" variant="h6">
  287. <FormattedMessage id="forgotUserPassword"/>?
  288. </Typography>
  289. </Link>
  290. </Stack>
  291. </Grid>
  292. <Grid item xs={12}>
  293. <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
  294. <Button onClick={() => getQRWithIAmSmart()} color="iAmSmart" fullWidth size="large" variant="outlined" startIcon={<img src={iAmSmartICon} alt="iAM Smart" width="30" />}>
  295. <Typography variant="h5">
  296. <FormattedMessage id="iAmSmartLogin"/>
  297. </Typography>
  298. </Button>
  299. </Stack>
  300. </Grid>
  301. <Grid item xs={12}>
  302. <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
  303. <Link href="https://www.iamsmart.gov.hk/tc/"><Typography align="center" variant="h6">
  304. { intl.formatMessage({id: 'learnMore'})+" >"}
  305. </Typography></Link>
  306. </Stack>
  307. </Grid>
  308. <Grid item xs={12}>
  309. <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
  310. <Button fullWidth size="large" variant="outlined" href="/register" ><Typography variant="h5">
  311. <FormattedMessage id="createOrReActivate"/>
  312. </Typography></Button>
  313. </Stack>
  314. </Grid>
  315. </Grid>
  316. <PasswordAlertDialog open={open} handleClose={handleClose} errorMassage={errorMassage} />
  317. </form>
  318. </FormikProvider>
  319. );
  320. };
  321. export default AuthLoginCustom;