Browse Source

Merge remote-tracking branch 'remotes/origin/iAmSmart'

# Conflicts:
#	src/auth/utils.js
master
Anna Ho 1 year ago
parent
commit
2622b6297c
6 changed files with 512 additions and 55 deletions
  1. +47
    -0
      src/auth/utils.js
  2. +127
    -55
      src/pages/authentication/RegisterCustom.js
  3. +185
    -0
      src/pages/iAmSmart/AuthCallback/index.js
  4. +66
    -0
      src/pages/iAmSmart/FailCallback/index.js
  5. +66
    -0
      src/pages/iAmSmart/SuccessCallback/index.js
  6. +21
    -0
      src/routes/LoginRoutes.js

+ 47
- 0
src/auth/utils.js View File

@@ -14,6 +14,53 @@ export const apiPath = window.location.hostname.match("localhost")?`${hostPath}/
//export const apiPath = `/api`;
export const paymentPath = `https://pnspsdev.gld.gov.hk/payment`;
export const iAmSmartPath = `https://<iAM_Smart_domain>`;
export const clientId = "cf61fa7c121e4869966f69c8694b1cd2";

export const iAmSmartCallbackPath = () => {
let hostname = window.location.hostname;
if (hostname.match("localhost")) {
hostname = "pnspsuat.gld.gov.hk";
}
return hostname;
};

export const getNonce = () => {
let hostname = window.location.hostname;
if (hostname.match("localhost")) {
hostname = "pnspsuat.gld.gov.hk";
}
return hostname;
};

export const getBowerType = () => {
console.log(navigator.userAgent)
// const regex = /Mobi|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Mi|huawei|Opera Mini|SAMSUNG|Samsung|SGH-[I|N|T]|GT-[I|N]|SM-[A|N|P|T|Z]|SHV-E|SCH-[I|J|R|S]|SPH-L/i;
// if(!regex.test(navigator.userAgent))

if (navigator.userAgent.indexOf("Edg") != -1) {
if (navigator.userAgent.match(/Android/i)) return "Android_Edge"
if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) return "iOS_Edge"
return "PC_Browser"
} else if (navigator.userAgent.indexOf("Chrome") != -1) {
if (navigator.userAgent.match(/Android/i)) return "Android_Chrome"
if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) return "iOS_Chrome"
return "PC_Browser"
} else if (navigator.userAgent.indexOf("Safari") != -1) {
if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) return "iOS_Safari"
return "PC_Browser"
} else if (navigator.userAgent.indexOf("Firefox") != -1) {
if (navigator.userAgent.match(/Android/i)) return "Android_Firefox"
if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) return "iOS_Firefox"
return "PC_Browser"
} else if (navigator.userAgent.match(/SAMSUNG|Samsung|SGH-[I|N|T]|GT-[I|N]|SM-[A|N|P|T|Z]|SHV-E|SCH-[I|J|R|S]|SPH-L/i)) {
return "Android_Samsung"
} else if (navigator.userAgent.match(/huawei/i)) {
return "Android_Huawei"
} else if (navigator.userAgent.match(/Mi/i)) {
return "Android_Xiaomi"
}
return "PC_Browser";
}


export const isUserLoggedIn = () => {


+ 127
- 55
src/pages/authentication/RegisterCustom.js View File

@@ -1,76 +1,148 @@


// material-ui
import {Link, Button, Card , Box, Grid } from '@mui/material';
import {
Dialog, DialogTitle, DialogContent,
Link, Button, Card, Box, Grid
} from '@mui/material';
import Typography from '@mui/material/Typography';
import iAmSmartICon from 'assets/images/icons/icon_iAmSmart.png';
import banner from 'assets/images/bg_ml.jpg';
import { Stack } from '../../../node_modules/@mui/material/index';
import { iAmSmartPath, clientId, getBowerType , iAmSmartCallbackPath} from 'auth/utils'

import * as React from 'react';

// ================================|| LOGIN ||================================ //

const RegisterCustom = () => (
<Stack justifyContent="center" sx={{ minHeight: '100vh', bgcolor: 'backgroundColor.default' }}>
<img src={banner} alt="banner" width="100%" height="200px"/>
<center>
<Card
sx={{
maxWidth: { xs: 1, lg: 1000 },
margin: { xs: 2.5, md: 3 },
'& > *': {
flexGrow: 1,
flexBasis: '50%'
},
backgroundColor: "secondary"
}}
>
<Box alignItems="center">
<Grid container>
<Grid item xs={12} md={12}>
<Typography mt={4} variant="h2">立即成為<strong style={{color: '#FF5733'}}>憲報刊登公告</strong>用戶</Typography>
<Typography variant="body1">只需4-5分鐘</Typography>
</Grid>
</Grid>
<Box mt={3} mb={3}>
<Grid container >
<Grid item xs={12} md={6} sx={{ borderRight: 1 , borderColor: 'grey.500' }}>
<Typography mb={4} variant="h3">個人用戶</Typography>
<Button variant="outlined" startIcon={<img src={iAmSmartICon} alt="iAM Smart" width="30" />}><Typography variant="h5">以「智方便」繼續</Typography></Button>
<Box mt={4} ml={2} mr={2} bgcolor="grey.100" p={1.5} >
<Typography textAlign='justify' variant="body1" display="block" gutterBottom>
你可點擊「智方便」按鈕,系統會自動輸入個人資料,或自行輸入個人資料,以即時啟動憲報刊登公告帳戶。
<br/>如欲使用「智方便」提供個人資料,請先下載「智方便」流動應用程式並登記成為「智方便」用戶。
</Typography>
<Link href="#">了解更多</Link>
</Box>
const RegisterCustom = () => {

const [isPopUp, setIsPopUp] = React.useState(false);


const registerWithIAmSmart = () => {
setIsPopUp(true);
}

const getQRWithIAmSmart = () => {
let callbackUrl = "https://"+iAmSmartCallbackPath()+"/iamsmart/authcallback";
let url = iAmSmartPath + "/api/v1/auth/getQR"
+ "?clientID=" + clientId
+ "&responseType=code"
+"&source=" + getBowerType()
+"&redirectURI="+encodeURIComponent(callbackUrl)
+"&scope="+encodeURIComponent("eidapi_auth eidapi_profiles")
+"&lang=zh-HK"//en-US, zh-HK, or zh-CN
//+"&state="
+"&brokerPage=false"
window.location.assign(url);
}


<Typography m={5}>或</Typography>
return (
<Stack justifyContent="center" sx={{ minHeight: '100vh', bgcolor: 'backgroundColor.default' }}>
<img src={banner} alt="banner" width="100%" height="200px" />
<center>
<Card
sx={{
maxWidth: { xs: 1, lg: 1000 },
margin: { xs: 2.5, md: 3 },
'& > *': {
flexGrow: 1,
flexBasis: '50%'
},
backgroundColor: "secondary"
}}

<Button href="/registerFrom" variant="contained"><Typography variant="h5">申請個人用戶</Typography></Button>
>
<Box alignItems="center">
<Grid container>
<Grid item xs={12} md={12}>
<Typography mt={4} variant="h2">立即成為<strong style={{ color: '#FF5733' }}>憲報刊登公告</strong>用戶</Typography>
<Typography variant="body1">只需4-5分鐘</Typography>
</Grid>
</Grid>
<Box mt={3} mb={3}>
<Grid container >
<Grid item xs={12} md={6} sx={{ borderRight: 1, borderColor: 'grey.500' }}>
<Typography mb={4} variant="h3">個人用戶</Typography>
<Button variant="outlined" onClick={registerWithIAmSmart} startIcon={<img src={iAmSmartICon} alt="iAM Smart" width="30" />}><Typography variant="h5">以「智方便」繼續</Typography></Button>

<Box mt={4} ml={2} mr={2} bgcolor="grey.100" p={1.5} >
<Typography textAlign='justify' variant="body1" display="block" gutterBottom>
你可點擊「智方便」按鈕,系統會自動輸入個人資料,或自行輸入個人資料,以即時啟動憲報刊登公告帳戶。
<br />如欲使用「智方便」提供個人資料,請先下載「智方便」流動應用程式並登記成為「智方便」用戶。
</Typography>
<Link href="https://www.iamsmart.gov.hk/tc/">了解更多</Link>
</Box>

<Typography m={5}>或</Typography>

<Button href="/registerFrom" variant="contained"><Typography variant="h5">申請個人用戶</Typography></Button>

<Typography ml={4} mr={4} mt={4} variant="body1" display="block" sx={{fontWeight: 'bold'}} gutterBottom>
需上載身份證明文件數碼檔案以進行網上申請。
<br/>如:香港身份證; 護照; 中國內地身份證; 專業執業証書等
<Typography ml={4} mr={4} mt={4} variant="body1" display="block" sx={{ fontWeight: 'bold' }} gutterBottom>
需上載身份證明文件數碼檔案以進行網上申請。
<br />如:香港身份證; 護照; 中國內地身份證; 專業執業証書等
</Typography>
</Grid>
<Grid item xs={12} md={6} sx={{ borderLeft: 1, borderColor: 'grey.500' }}>
<Typography mb={4} variant="h3">機構/公司用戶</Typography>
<Button href="/registerFromOrganization" variant="contained" sx={{ mt: 0.5 }}><Typography variant="h5">申請機構/公司用戶</Typography></Button>
<Typography ml={4} mr={4} mt={4} variant="body1" display="block" sx={{ fontWeight: 'bold' }} gutterBottom>
需上載以下任何一份證明文件以進行網上申請。
<br />如:商業登記證;專業執業證書
</Typography>
</Grid>
</Grid>
</Box>
</Box>
</Card >
</center>
<Dialog open={isPopUp} onClose={() => setIsPopUp(false)} >
<DialogTitle></DialogTitle>
<DialogContent>
<Typography variant="h3" >
授權「智方便」提供個人資料
</Typography>
<Typography variant="h5" style={{ padding: '4px' }}>
為完成開戶並建立與「智方便」的連接,請授權「智方便」提供以下個人資料:
</Typography>
</DialogContent>
<DialogContent >
<Grid container style={{ paddingLeft: '16px' }}>
<Grid item xs={6} >
<Typography style={{ padding: '4px' }}>
- 中文姓名
</Typography>
<Typography style={{ padding: '4px' }}>
- 英文姓名
</Typography>
<Typography style={{ padding: '4px' }}>
- 身份證號碼
</Typography>
</Grid>
<Grid item xs={12} md={6} sx={{ borderLeft: 1 ,borderColor: 'grey.500' }}>
<Typography mb={4} variant="h3">機構/公司用戶</Typography>
<Button href="/registerFromOrganization" variant="contained" sx={{mt:0.5}}><Typography variant="h5">申請機構/公司用戶</Typography></Button>
<Typography ml={4} mr={4} mt={4} variant="body1" display="block" sx={{fontWeight: 'bold'}} gutterBottom>
需上載以下任何一份證明文件以進行網上申請。
<br/>如:商業登記證;專業執業證書
<Grid item xs={6} >
<Typography style={{ padding: '4px' }}>
- 電郵地址
</Typography>
<Typography style={{ padding: '4px' }}>
- 手機號碼
</Typography>
<Typography style={{ padding: '4px' }}>
- 住宅地址
</Typography>
</Grid>
</Grid>
</Box>
</Box>
</Card >
</center>
</Stack>
</DialogContent>
<DialogContent align="right">
<Button variant="outlined" onClick={getQRWithIAmSmart} startIcon={<img src={iAmSmartICon} alt="iAM Smart" width="30" />}><Typography variant="h5">使用「智方便」自動填表</Typography></Button>
<br />
<Link href="https://www.iamsmart.gov.hk/tc/">了解更多</Link>
</DialogContent>
</Dialog>
</Stack>
);

);
};

export default RegisterCustom;

+ 185
- 0
src/pages/iAmSmart/AuthCallback/index.js View File

@@ -0,0 +1,185 @@
// material-ui
import {
Grid,
Typography,
Stack,
Card,
FormHelperText,
InputLabel, OutlinedInput,
} from '@mui/material';
import * as React from "react";
import { useFormik, FormikProvider } from 'formik';
import * as yup from 'yup';
import { useParams } from "react-router-dom";
//import { iAmSmartPath, clientId, getBowerType , iAmSmartCallbackPath} from 'auth/utils'

import Loadable from 'components/Loadable';
const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent')));

import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png'
const BackgroundHead = {
backgroundImage: `url(${titleBackgroundImg})`,
width: '100%',
height: '100%',
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
backgroundColor: '#0C489E',
backgroundPosition: 'right'
}

// ==============================|| DASHBOARD - DEFAULT ||============================== //

const Index = () => {

const params = useParams();
const [onReady, setOnReady] = React.useState(false);
const [checkUsername, setCheckUsername] = React.useState(false);
const [props, setProps] = React.useState({});

React.useEffect(() => {
if(params.code){
setOnReady(true);
setProps({});
}
}, []);

// function loadIAmSmartProfile(){

// }

function displayErrorMsg(errorMsg) {
return <Typography variant="errorMessage1">{errorMsg}</Typography>
}

const formik = useFormik({
initialValues: ({
username: '',
enName: '',
email: '',
address1: '',
address2: '',
address3: '',
password: '',
phone: '',
phoneCountryCode: '852',
}),

validationSchema: yup.object().shape({
username: yup.string().min(6, displayErrorMsg('用戶名稱最少6位')).required(displayErrorMsg('請輸入用戶名稱'))
.matches(/^[aA-zZ0-9\s]+$/, { message: displayErrorMsg("用戶名稱不包含特殊字符") })
.matches(/^\S*$/, { message: displayErrorMsg('用戶名稱不包含空格') }),
enName: yup.string().max(255).required(displayErrorMsg('請輸入英文姓名')),
chName: yup.string().max(255).required(displayErrorMsg('請輸入中文姓名')),
address1: yup.string().max(255).required(displayErrorMsg('請輸入第一行地址')),
address2: yup.string().max(255).required(displayErrorMsg('請輸入第二行地址')),
address3: yup.string().max(255).required(displayErrorMsg('請輸入第三行地址')),
email: yup.string().email(displayErrorMsg('請輸入電郵格式')).max(255).required(displayErrorMsg('請輸入電郵')),
phoneCountryCode: yup.string().min(2, displayErrorMsg('請輸入最少2位數字')).required(displayErrorMsg('請輸入國際區號')),
phone: yup.string().min(8, displayErrorMsg('請輸入最少8位數字')).required(displayErrorMsg('請輸入聯絡電話')),
}, ['username']),

});


return (
!onReady ?
<LoadingComponent />
:
<FormikProvider value={formik}>
<Grid container sx={{ minHeight: '110vh', backgroundColor: '#fff' }} direction="column" justifyContent="flex-start" alignItems="center" style={{ backgroundColor: "#F2F2F2" }} >
<Grid item xs={12} width="100%">
<div style={BackgroundHead} width="100%">
<Stack direction="row" height='70px'>
<Typography ml={15} color='#FFF' variant="h4" sx={{ pt: 2 }}>iAmSmart 登記</Typography>
</Stack>
</div>
</Grid>
{/*row 1*/}
<Grid item xs={12} md={12}>
<Grid container justifyContent="flex-start" alignItems="center" >
<Card
sx={{
maxWidth: { xs: 1, lg: 1000 },
margin: { xs: 2.5, md: 3 },
'& > *': {
flexGrow: 1,
flexBasis: '50%'
},
backgroundColor: "secondary",
p:8,
pl:16,
pr:16
}}
>
<Grid container spacing={3} >
<Grid item xs={12} md={12}>
<Stack direction="column" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
<div style={{ borderBottom: "3px solid #1A4399", width: "100%", margin_right: "15px" }}>
<Typography display="inline" variant="h3" sx={{ color: '#1A4399' }}>成為個人用戶</Typography>
</div>
<Typography mt={0.25} variant="h6" sx={{ color: '#f10000' }}>註有*的項目必須輸入資料</Typography>
<Typography mt={0.25} variant="h4" sx={{ color: 'primary.primary' }}>用戶資料</Typography>
{/* <Typography component={Link} to="/login" variant="body1" sx={{ textDecoration: 'none' }} color="primary">
Already have an account?
</Typography> */}
</Stack>
</Grid>
<Grid item xs={12} md={12}>
<Grid container spacing={1}>
<Grid item xs={12} md={12} >
<Stack spacing={1}>
<InputLabel htmlFor="username-signup">
<Typography variant="h5">
用戶登入名稱
<span style={{ color: '#f10000' }}>*</span>
</Typography>
</InputLabel>
<OutlinedInput
id="username-login"
type="text"
value={formik.values.username.trim()}
name="username"
onChange={(e) => {
setCheckUsername(false)
props.username = e.target.value
formik.handleChange(e)
}}
placeholder="用戶登入名稱"
fullWidth
error={Boolean((formik.touched.username && formik.errors.username) || checkUsername)}
onBlur={formik.handleBlur}
inputProps={{
onKeyDown: (e) => {
if (e.key === 'Enter') {
e.preventDefault();
}
},
}}
/>
{formik.touched.username && formik.errors.username && (
<FormHelperText error id="helper-text-username-signup">
{formik.errors.username}
</FormHelperText>
)}
{checkUsername && (
<FormHelperText error id="helper-text-username-signup">
此用戶登入名稱已被注冊,請使用其他用戶登入名稱
</FormHelperText>
)}
</Stack>
</Grid>

</Grid>
</Grid>
</Grid>
</Card>
</Grid>
</Grid>
{/*row 2*/}
</Grid >
</FormikProvider>

);
};

export default Index;

+ 66
- 0
src/pages/iAmSmart/FailCallback/index.js View File

@@ -0,0 +1,66 @@
// material-ui
import {
Grid,
Typography,
Stack,
} from '@mui/material';
import * as React from "react";

import Loadable from 'components/Loadable';
const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent')));

import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png'
const BackgroundHead = {
backgroundImage: `url(${titleBackgroundImg})`,
width: '100%',
height: '100%',
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
backgroundColor: '#0C489E',
backgroundPosition: 'right'
}

// ==============================|| DASHBOARD - DEFAULT ||============================== //

const Index = () => {

const [onReady, setOnReady] = React.useState(false);

React.useEffect(() => {
setOnReady(true);
}, []);

return (
!onReady ?
<LoadingComponent />
:
<Grid container sx={{ minHeight: '110vh', backgroundColor: '#fff' }} direction="column" justifyContent="flex-start" alignItems="center" >
<Grid item xs={12} width="100%">
<div style={BackgroundHead} width="100%">
<Stack direction="row" height='70px'>
<Typography ml={15} color='#FFF' variant="h4" sx={{ pt: 2 }}>iAmSmart 登入失敗</Typography>
</Stack>
</div>
</Grid>
{/*row 1*/}
<Grid item xs={12} md={12} >
<Grid container justifyContent="flex-start" alignItems="center" >
<center>
<Grid item xs={12} md={12} >

<Typography variant="h3" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}>
連接 iAM Smart 時出現錯誤,請重試。
</Typography>

</Grid>
</center>
</Grid>
</Grid>
{/*row 2*/}
</Grid >

);
};

export default Index;

+ 66
- 0
src/pages/iAmSmart/SuccessCallback/index.js View File

@@ -0,0 +1,66 @@
// material-ui
import {
Grid,
Typography,
Stack,
} from '@mui/material';
import * as React from "react";

import Loadable from 'components/Loadable';
const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent')));

import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png'
const BackgroundHead = {
backgroundImage: `url(${titleBackgroundImg})`,
width: '100%',
height: '100%',
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
backgroundColor: '#0C489E',
backgroundPosition: 'right'
}

// ==============================|| DASHBOARD - DEFAULT ||============================== //

const Index = () => {

const [onReady, setOnReady] = React.useState(false);

React.useEffect(() => {
setOnReady(true);
}, []);

return (
!onReady ?
<LoadingComponent />
:
<Grid container sx={{ minHeight: '110vh', backgroundColor: '#fff' }} direction="column" justifyContent="flex-start" alignItems="center" >
<Grid item xs={12} width="100%">
<div style={BackgroundHead} width="100%">
<Stack direction="row" height='70px'>
<Typography ml={15} color='#FFF' variant="h4" sx={{ pt: 2 }}>iAmSmart 成功登入</Typography>
</Stack>
</div>
</Grid>
{/*row 1*/}
<Grid item xs={12} md={12} >
<Grid container justifyContent="flex-start" alignItems="center" >
<center>
<Grid item xs={12} md={12} >

<Typography variant="h3" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}>
iAmSmart 成功登入
</Typography>

</Grid>
</center>
</Grid>
</Grid>
{/*row 2*/}
</Grid >

);
};

export default Index;

+ 21
- 0
src/routes/LoginRoutes.js View File

@@ -11,6 +11,11 @@ const AuthRegister = Loadable(lazy(() => import('pages/authentication/RegisterCu
const RegisterForm = Loadable(lazy(() => import('pages/authentication/Register')));
const BusRegisterForm = Loadable(lazy(() => import('pages/authentication/BusRegister')));
const ErrorPage = Loadable(lazy(() => import('pages/extra-pages/ErrorPage')));
const IAmSmart_FailCallback = Loadable(lazy(() => import('pages/iAmSmart/FailCallback')));
const IAmSmart_SuccessCallback = Loadable(lazy(() => import('pages/iAmSmart/SuccessCallback')));
const IAmSmart_AuthCallback = Loadable(lazy(() => import('pages/iAmSmart/AuthCallback')));

//TODO: this page for testing only, please remove at prod
const TestMailPage = Loadable(lazy(() => import('pages/pnspsNotifyTest')));
const VerifyPage = Loadable(lazy(() => import('pages/authentication/Verify')));

@@ -20,6 +25,10 @@ const LoginRoutes = {
path: '/',
element: <MainLayout />,
children: [
{//TODO: this page for testing only, please remove at prod
path: 'testMailPage',
element: <TestMailPage/>
},
{
path: 'login',
element: <AuthLogin />
@@ -40,6 +49,18 @@ const LoginRoutes = {
path: 'error',
element: <ErrorPage/>
},
{
path: 'iamsmart/authcallback',
element: <IAmSmart_AuthCallback/>
},
{
path: 'iamsmart/loginfallback',
element: <IAmSmart_FailCallback/>
},
{
path: 'iamsmart/logincallback',
element: <IAmSmart_SuccessCallback/>
},
{
path: 'testMailPage',
element: <TestMailPage/>


Loading…
Cancel
Save