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

415 行
14 KiB

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