yaml-admin-front 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -20
- package/src/AdminContext.jsx +14 -0
- package/src/YMLAdmin.jsx +55 -0
- package/src/common/axios.jsx +78 -0
- package/src/common/client.jsx +78 -0
- package/src/index.js +1 -0
- package/src/layout/MyAppBar.jsx +60 -0
- package/src/layout/MyLayout.jsx +8 -0
- package/src/layout/MyMenu.jsx +62 -0
- package/src/layout/SubMenu.jsx +65 -0
- package/src/login/LoginPage.jsx +155 -0
- package/src/login/authprovider.jsx +76 -0
- package/README.md +0 -8
- package/dist/assets/index-DnQ2AVh_.js +0 -309
- package/dist/assets/index-Dtn62Xmo.css +0 -1
- package/dist/index.html +0 -14
- package/dist/vite.svg +0 -1
- package/index.html +0 -13
- package/src/App.css +0 -42
- package/src/App.jsx +0 -24
- package/src/assets/react.svg +0 -1
- package/src/index.css +0 -68
- package/src/main.jsx +0 -10
- package/vite.config.js +0 -15
package/package.json
CHANGED
|
@@ -1,40 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yaml-admin-front",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
"build": "vite build",
|
|
9
|
-
"lint": "eslint .",
|
|
10
|
-
"preview": "vite preview"
|
|
11
|
-
},
|
|
5
|
+
"description": "React components for yaml-admin front (library)",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"scripts": {},
|
|
12
8
|
"dependencies": {
|
|
9
|
+
"@iconify/react": "^6.0.0",
|
|
10
|
+
"axios": "^1.11.0",
|
|
11
|
+
"js-yaml": "^4.1.0",
|
|
13
12
|
"ra-data-json-server": "^5.10.1",
|
|
14
13
|
"react": "^19.1.1",
|
|
15
14
|
"react-admin": "^5.10.1",
|
|
16
|
-
"react-dom": "^19.1.1"
|
|
15
|
+
"react-dom": "^19.1.1",
|
|
16
|
+
"yaml": "^2.8.1"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@eslint/js": "^9.32.0",
|
|
20
19
|
"@types/react": "^19.1.9",
|
|
21
|
-
"@types/react-dom": "^19.1.7"
|
|
22
|
-
"@vitejs/plugin-react": "^4.7.0",
|
|
23
|
-
"eslint": "^9.32.0",
|
|
24
|
-
"eslint-plugin-react-hooks": "^5.2.0",
|
|
25
|
-
"eslint-plugin-react-refresh": "^0.4.20",
|
|
26
|
-
"globals": "^16.3.0",
|
|
27
|
-
"vite": "^7.1.0"
|
|
20
|
+
"@types/react-dom": "^19.1.7"
|
|
28
21
|
},
|
|
29
22
|
"repository": {
|
|
30
23
|
"type": "git",
|
|
31
24
|
"url": "git+https://github.com/muyoungko/yaml_admin.git"
|
|
32
25
|
},
|
|
33
26
|
"files": [
|
|
34
|
-
"dist",
|
|
35
27
|
"src",
|
|
36
|
-
"index.html",
|
|
37
|
-
"vite.config.js",
|
|
38
28
|
"README.md"
|
|
39
29
|
],
|
|
40
30
|
"publishConfig": {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React, { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
// Context to share parsed admin YAML JSON across the app
|
|
4
|
+
export const AdminContext = createContext({ yml: null, setYml: () => {} });
|
|
5
|
+
|
|
6
|
+
export const AdminProvider = ({ initialYml = null, children }) => {
|
|
7
|
+
return <AdminContext.Provider value={initialYml}>{children}</AdminContext.Provider>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const useAdminContext = () => {
|
|
11
|
+
return useContext(AdminContext);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
|
package/src/YMLAdmin.jsx
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Admin, Resource, ListGuesser } from "react-admin";
|
|
2
|
+
import jsonServerProvider from "ra-data-json-server";
|
|
3
|
+
import YAML from 'yaml';
|
|
4
|
+
import LoginPage from './login/LoginPage';
|
|
5
|
+
import MyLayout from './layout/MyLayout'
|
|
6
|
+
import { useState, useEffect } from 'react';
|
|
7
|
+
import { Icon } from '@iconify/react';
|
|
8
|
+
import { AdminProvider } from './AdminContext';
|
|
9
|
+
import authProvider from './login/authProvider';
|
|
10
|
+
import { setApiHost} from './common/axios';
|
|
11
|
+
|
|
12
|
+
const API_HOST = import.meta.env.VITE_HOST_API || 'http://localhost:6911'
|
|
13
|
+
const dataProvider = jsonServerProvider(API_HOST);
|
|
14
|
+
|
|
15
|
+
const YMLAdmin = ({ adminYaml }) => {
|
|
16
|
+
const [yml, setYml] = useState(null);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const loadYamlFile = async () => {
|
|
19
|
+
try {
|
|
20
|
+
const json = YAML.parse(adminYaml);
|
|
21
|
+
setYml(json);
|
|
22
|
+
setApiHost(json['api-host'].uri);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error('YAML 파일을 읽는 중 오류가 발생했습니다:', error);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
loadYamlFile();
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<AdminProvider initialYml={yml} width="1250px">
|
|
33
|
+
<Admin
|
|
34
|
+
layout={MyLayout}
|
|
35
|
+
authProvider={authProvider}
|
|
36
|
+
dataProvider={dataProvider}>
|
|
37
|
+
{yml?.entity && Object.keys(yml.entity).map(name => {
|
|
38
|
+
const entity = yml.entity[name];
|
|
39
|
+
const IconComponent = entity?.icon
|
|
40
|
+
? () => <Icon icon={entity.icon} width="1.25rem" height="1.25rem" />
|
|
41
|
+
: undefined;
|
|
42
|
+
return (
|
|
43
|
+
<Resource key={name} name={name}
|
|
44
|
+
options={{ label: entity.label }}
|
|
45
|
+
icon={IconComponent}
|
|
46
|
+
list={ListGuesser} />
|
|
47
|
+
)
|
|
48
|
+
})}
|
|
49
|
+
|
|
50
|
+
</Admin>
|
|
51
|
+
</AdminProvider>
|
|
52
|
+
)
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default YMLAdmin;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
// ----------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
const axiosInstance = axios.create({});
|
|
6
|
+
|
|
7
|
+
export const setApiHost = (host) => {
|
|
8
|
+
let base = host ?? import.meta.env.VITE_HOST_API ?? 'http://localhost:6911';
|
|
9
|
+
if (base && !base.startsWith('http')) {
|
|
10
|
+
base = `http://${base}`;
|
|
11
|
+
}
|
|
12
|
+
axiosInstance.defaults.baseURL = base;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// initialize with defaults so it's usable before YAML is loaded
|
|
16
|
+
setApiHost();
|
|
17
|
+
|
|
18
|
+
axiosInstance.interceptors.response.use(
|
|
19
|
+
(res) => res,
|
|
20
|
+
(error) => Promise.reject((error.response && error.response.data) || 'Something went wrong')
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export default axiosInstance;
|
|
24
|
+
|
|
25
|
+
// ----------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
export const fetcher = async (args) => {
|
|
28
|
+
const [url, config] = Array.isArray(args) ? args : [args];
|
|
29
|
+
|
|
30
|
+
const res = await axiosInstance.get(url, { ...config });
|
|
31
|
+
|
|
32
|
+
return res.data;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const postFetcher = async (url, config, param) => {
|
|
36
|
+
const res = await axiosInstance.post(url, param, { ...config });
|
|
37
|
+
|
|
38
|
+
return res.data;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const putFetcher = async (url, config, param) => {
|
|
42
|
+
const res = await axiosInstance.put(url, param, { ...config });
|
|
43
|
+
|
|
44
|
+
return res.data;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const uploadFile = (blob_path, file_name) => new Promise((resolve, reject) => {
|
|
48
|
+
if(blob_path){
|
|
49
|
+
const reader = new FileReader();
|
|
50
|
+
reader.readAsArrayBuffer(blob_path);
|
|
51
|
+
reader.onload = async () => {
|
|
52
|
+
const ext = file_name.substring(file_name.lastIndexOf('.')+1, file_name.length);
|
|
53
|
+
const res = await fetcher(`/api/media/url/put/${ext}`)
|
|
54
|
+
fetch(res.upload_url, { method: "PUT",
|
|
55
|
+
headers: {
|
|
56
|
+
'Content-Type': res.contentType
|
|
57
|
+
},
|
|
58
|
+
body:reader.result })
|
|
59
|
+
.then(res2=>{
|
|
60
|
+
resolve(res.key)
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
reader.onerror = reject;
|
|
64
|
+
} else {
|
|
65
|
+
resolve(null);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export const convertFileToBase64 = file => new Promise((resolve, reject) => {
|
|
70
|
+
if(file){
|
|
71
|
+
const reader = new FileReader();
|
|
72
|
+
reader.readAsDataURL(file);
|
|
73
|
+
reader.onload = () => resolve(reader.result);
|
|
74
|
+
reader.onerror = reject;
|
|
75
|
+
} else {
|
|
76
|
+
reject(new Error('no file'))
|
|
77
|
+
}
|
|
78
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
// ----------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
const axiosInstance = axios.create({});
|
|
6
|
+
|
|
7
|
+
export const setUpApiHost = (host) => {
|
|
8
|
+
let base = host ?? import.meta.env.VITE_HOST_API ?? 'http://localhost:6911';
|
|
9
|
+
if (base && !base.startsWith('http')) {
|
|
10
|
+
base = `http://${base}`;
|
|
11
|
+
}
|
|
12
|
+
axiosInstance.defaults.baseURL = base;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// initialize with defaults so it's usable before YAML is loaded
|
|
16
|
+
setUpApiHost();
|
|
17
|
+
|
|
18
|
+
axiosInstance.interceptors.response.use(
|
|
19
|
+
(res) => res,
|
|
20
|
+
(error) => Promise.reject((error.response && error.response.data) || 'Something went wrong')
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export default axiosInstance;
|
|
24
|
+
|
|
25
|
+
// ----------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
export const fetcher = async (args) => {
|
|
28
|
+
const [url, config] = Array.isArray(args) ? args : [args];
|
|
29
|
+
|
|
30
|
+
const res = await axiosInstance.get(url, { ...config });
|
|
31
|
+
|
|
32
|
+
return res.data;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const postFetcher = async (url, config, param) => {
|
|
36
|
+
const res = await axiosInstance.post(url, param, { ...config });
|
|
37
|
+
|
|
38
|
+
return res.data;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const putFetcher = async (url, config, param) => {
|
|
42
|
+
const res = await axiosInstance.put(url, param, { ...config });
|
|
43
|
+
|
|
44
|
+
return res.data;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const uploadFile = (blob_path, file_name) => new Promise((resolve, reject) => {
|
|
48
|
+
if(blob_path){
|
|
49
|
+
const reader = new FileReader();
|
|
50
|
+
reader.readAsArrayBuffer(blob_path);
|
|
51
|
+
reader.onload = async () => {
|
|
52
|
+
const ext = file_name.substring(file_name.lastIndexOf('.')+1, file_name.length);
|
|
53
|
+
const res = await fetcher(`/api/media/url/put/${ext}`)
|
|
54
|
+
fetch(res.upload_url, { method: "PUT",
|
|
55
|
+
headers: {
|
|
56
|
+
'Content-Type': res.contentType
|
|
57
|
+
},
|
|
58
|
+
body:reader.result })
|
|
59
|
+
.then(res2=>{
|
|
60
|
+
resolve(res.key)
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
reader.onerror = reject;
|
|
64
|
+
} else {
|
|
65
|
+
resolve(null);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export const convertFileToBase64 = file => new Promise((resolve, reject) => {
|
|
70
|
+
if(file){
|
|
71
|
+
const reader = new FileReader();
|
|
72
|
+
reader.readAsDataURL(file);
|
|
73
|
+
reader.onload = () => resolve(reader.result);
|
|
74
|
+
reader.onerror = reject;
|
|
75
|
+
} else {
|
|
76
|
+
reject(new Error('no file'))
|
|
77
|
+
}
|
|
78
|
+
});
|
package/src/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as YMLAdmin } from './YMLAdmin.jsx';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React, { forwardRef, useState, useEffect } from 'react'
|
|
2
|
+
import { AppBar, UserMenu, Logout, MenuItemLink, useRefresh, useUserMenu, LoadingIndicator } from 'react-admin';
|
|
3
|
+
import Avatar from '@mui/material/Avatar';
|
|
4
|
+
// import client from '../common/client'
|
|
5
|
+
import { Badge, Typography, Toolbar, Tabs, Tab, Button, Box, List as CoreList, Chip, CircularProgress } from '@mui/material';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const ProjectMenu = forwardRef((props, ref) => {
|
|
9
|
+
const { onClose } = useUserMenu();
|
|
10
|
+
const refresh = useRefresh();
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
}, [])
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<MenuItemLink
|
|
17
|
+
ref={ref}
|
|
18
|
+
to="/screen"
|
|
19
|
+
primaryText={<Typography color={selected ? 'primary' : ''}>{props.project.name}</Typography>}
|
|
20
|
+
leftIcon={<MyCustomIcon url={props.project.img && props.project.img.src} />}
|
|
21
|
+
onClick={handleClick} // close the menu on click
|
|
22
|
+
/>
|
|
23
|
+
)
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const MyUserMenu = props => {
|
|
27
|
+
const [projectList, setProjectList] = useState([]);
|
|
28
|
+
const [url, setUrl] = useState({});
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
|
|
32
|
+
}, [])
|
|
33
|
+
|
|
34
|
+
const handleChange = () => {
|
|
35
|
+
let selected_project = getSelectedProject()
|
|
36
|
+
projectList.map(m => {
|
|
37
|
+
if (selected_project == m.id)
|
|
38
|
+
if (m.img)
|
|
39
|
+
setUrl(m.img.src)
|
|
40
|
+
else
|
|
41
|
+
setUrl(null)
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
return (
|
|
45
|
+
<UserMenu {...props}>
|
|
46
|
+
{projectList.map(m => (
|
|
47
|
+
<ProjectMenu key={m.id} project={m} onChange={handleChange} />
|
|
48
|
+
))}
|
|
49
|
+
<Logout />
|
|
50
|
+
</UserMenu>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
const MyAppBar = props => <AppBar {...props} userMenu={<MyUserMenu />} >
|
|
56
|
+
|
|
57
|
+
</AppBar>;
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
export default MyAppBar;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useState, useMemo } from 'react';
|
|
2
|
+
import { Menu } from 'react-admin';
|
|
3
|
+
import SubMenu from './SubMenu';
|
|
4
|
+
import { Icon } from '@iconify/react';
|
|
5
|
+
import { useAdminContext } from '../AdminContext';
|
|
6
|
+
|
|
7
|
+
const MyMenu = () => {
|
|
8
|
+
const yml = useAdminContext();
|
|
9
|
+
const [state, setState] = useState({
|
|
10
|
+
|
|
11
|
+
});
|
|
12
|
+
const handleToggle = (menu) => {
|
|
13
|
+
setState(state => ({ ...state, [menu]: !state[menu] }));
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const categoryList = useMemo(() => {
|
|
17
|
+
const list = yml?.front?.category || []
|
|
18
|
+
list.forEach(m=>{
|
|
19
|
+
state[m.name] = true
|
|
20
|
+
m.menuList = yml?.entity && Object.keys(yml.entity).map(m=>{
|
|
21
|
+
let r = yml.entity[m]
|
|
22
|
+
r.name = m
|
|
23
|
+
return r
|
|
24
|
+
}).filter(f=>f.category == m.name)
|
|
25
|
+
})
|
|
26
|
+
return list
|
|
27
|
+
}, [yml]);
|
|
28
|
+
|
|
29
|
+
const noCartegoryList = useMemo(() => {
|
|
30
|
+
const list = yml?.entity && Object.keys(yml.entity).map(m=>{
|
|
31
|
+
let r = yml.entity[m]
|
|
32
|
+
r.name = m
|
|
33
|
+
return r
|
|
34
|
+
}).filter(f=>!f.category)
|
|
35
|
+
return list || [];
|
|
36
|
+
}, [yml]);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Menu>
|
|
40
|
+
<Menu.DashboardItem />
|
|
41
|
+
|
|
42
|
+
{/* <Menu.ResourceItem name='member' /> */}
|
|
43
|
+
{categoryList.map(c => {
|
|
44
|
+
return <SubMenu
|
|
45
|
+
key={c.name}
|
|
46
|
+
handleToggle={() => handleToggle(c.name)}
|
|
47
|
+
isOpen={state[c.name]}
|
|
48
|
+
name={c.name}
|
|
49
|
+
icon={<Icon icon={c.icon} />}
|
|
50
|
+
dense={true}
|
|
51
|
+
>
|
|
52
|
+
{c.menuList.map(m => <Menu.ResourceItem key={m.name} name={m.name} />)}
|
|
53
|
+
</SubMenu>
|
|
54
|
+
})}
|
|
55
|
+
|
|
56
|
+
{noCartegoryList.map(m => <Menu.ResourceItem key={m.name} name={m.name} />)}
|
|
57
|
+
|
|
58
|
+
</Menu>
|
|
59
|
+
)
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default MyMenu;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ReactElement, ReactNode } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
List,
|
|
5
|
+
MenuItem,
|
|
6
|
+
ListItemIcon,
|
|
7
|
+
Typography,
|
|
8
|
+
Collapse,
|
|
9
|
+
Tooltip,
|
|
10
|
+
} from '@mui/material';
|
|
11
|
+
import ExpandMore from '@mui/icons-material/ExpandMore';
|
|
12
|
+
import { useTranslate, useSidebarState } from 'react-admin';
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
const SubMenu = (props) => {
|
|
16
|
+
const { handleToggle, isOpen, name, icon, children, dense } = props;
|
|
17
|
+
const translate = useTranslate();
|
|
18
|
+
|
|
19
|
+
const [sidebarIsOpen] = useSidebarState();
|
|
20
|
+
|
|
21
|
+
const header = (
|
|
22
|
+
<MenuItem dense={dense} onClick={handleToggle}>
|
|
23
|
+
<ListItemIcon sx={{ minWidth: 5 }}>
|
|
24
|
+
{isOpen ? <ExpandMore /> : icon}
|
|
25
|
+
</ListItemIcon>
|
|
26
|
+
<Typography variant="inherit" color="textSecondary">
|
|
27
|
+
{translate(name)}
|
|
28
|
+
</Typography>
|
|
29
|
+
</MenuItem>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div>
|
|
34
|
+
{sidebarIsOpen || isOpen ? (
|
|
35
|
+
header
|
|
36
|
+
) : (
|
|
37
|
+
<Tooltip title={translate(name)} placement="right">
|
|
38
|
+
{header}
|
|
39
|
+
</Tooltip>
|
|
40
|
+
)}
|
|
41
|
+
<Collapse in={isOpen} timeout="auto" unmountOnExit>
|
|
42
|
+
<List
|
|
43
|
+
dense={dense}
|
|
44
|
+
component="div"
|
|
45
|
+
disablePadding
|
|
46
|
+
className="SubMenu"
|
|
47
|
+
sx={{
|
|
48
|
+
'& .MuiMenuItem-root': {
|
|
49
|
+
transition:
|
|
50
|
+
'padding-left 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms',
|
|
51
|
+
paddingLeft: theme =>
|
|
52
|
+
sidebarIsOpen
|
|
53
|
+
? theme.spacing(4)
|
|
54
|
+
: theme.spacing(2),
|
|
55
|
+
},
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
</List>
|
|
60
|
+
</Collapse>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default SubMenu;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
useLogin,
|
|
4
|
+
useNotify
|
|
5
|
+
} from 'react-admin';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Box,
|
|
9
|
+
Typography
|
|
10
|
+
} from '@mui/material';
|
|
11
|
+
import client from '../common/axios.jsx';
|
|
12
|
+
|
|
13
|
+
const LoginPage = ({ theme }) => {
|
|
14
|
+
const login = useLogin();
|
|
15
|
+
const divRef = useRef(null);
|
|
16
|
+
|
|
17
|
+
const [loading, setLoading] = React.useState(false)
|
|
18
|
+
const [email, setEmail] = React.useState('')
|
|
19
|
+
const [password, setPassword] = React.useState('')
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
|
|
23
|
+
}, [])
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (divRef.current && window.google) {
|
|
27
|
+
window.google.accounts.id.initialize({
|
|
28
|
+
client_id: '886217236574-52ccfmm2nj12dc63lcfb0ksms2akkimm.apps.googleusercontent.com',
|
|
29
|
+
context:'signin',
|
|
30
|
+
itp_support: true,
|
|
31
|
+
ux_mode: "redirect",
|
|
32
|
+
login_uri:`${api_host}/google/login/callback`,
|
|
33
|
+
callback: (res, error) => {
|
|
34
|
+
// This is the function that will be executed once the authentication with google is finished
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
window.google.accounts.id.renderButton(divRef.current, {
|
|
38
|
+
// theme: 'filled_blue',
|
|
39
|
+
// size: 'medium',
|
|
40
|
+
// type: 'standard',
|
|
41
|
+
// text: 'continue_with',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}, [divRef.current, window.google]);
|
|
45
|
+
|
|
46
|
+
const handleSubmit = e => {
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const handleLogin = (e) => {
|
|
51
|
+
setLoading(true)
|
|
52
|
+
client.request_post('/member/login', {
|
|
53
|
+
type: 'force',
|
|
54
|
+
email: email,
|
|
55
|
+
pass: password,
|
|
56
|
+
}).then((res) => {
|
|
57
|
+
setLoading(false)
|
|
58
|
+
if (res && res.r) {
|
|
59
|
+
localStorage.setItem('token', res.token);
|
|
60
|
+
window.location.href = '/'
|
|
61
|
+
} else if (res) {
|
|
62
|
+
alert(res.msg)
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Box>
|
|
69
|
+
<form onSubmit={handleSubmit}>
|
|
70
|
+
<Box
|
|
71
|
+
display="flex"
|
|
72
|
+
justifyContent="center"
|
|
73
|
+
alignItems="center"
|
|
74
|
+
minHeight="100vh"
|
|
75
|
+
>
|
|
76
|
+
<Box>
|
|
77
|
+
<Box>
|
|
78
|
+
<img
|
|
79
|
+
width='200'
|
|
80
|
+
height='200'
|
|
81
|
+
alt='testA' />
|
|
82
|
+
</Box>
|
|
83
|
+
<Box mt={2}>
|
|
84
|
+
<Typography variant="h4" gutterBottom>
|
|
85
|
+
Devil App Builder
|
|
86
|
+
</Typography>
|
|
87
|
+
</Box>
|
|
88
|
+
<Box>
|
|
89
|
+
<Typography variant="subtitle1" gutterBottom>
|
|
90
|
+
Build your app by yourself
|
|
91
|
+
</Typography>
|
|
92
|
+
</Box>
|
|
93
|
+
{/* <Box mt={2}>
|
|
94
|
+
<TextField
|
|
95
|
+
fullWidth
|
|
96
|
+
variant="filled"
|
|
97
|
+
label="Google Email"
|
|
98
|
+
onChange={(e) => {setEmail(e.target.value)}}
|
|
99
|
+
defaultValue=""
|
|
100
|
+
/>
|
|
101
|
+
</Box>
|
|
102
|
+
<Box>
|
|
103
|
+
<TextField
|
|
104
|
+
fullWidth
|
|
105
|
+
variant="filled"
|
|
106
|
+
label="Password"
|
|
107
|
+
type="password"
|
|
108
|
+
onChange={(e) => {setPassword(e.target.value)}}
|
|
109
|
+
autoComplete="current-password"
|
|
110
|
+
/>
|
|
111
|
+
</Box>
|
|
112
|
+
<Box mt={1} sx={{ display: 'flex', justifyContent: 'flex-start' }}>
|
|
113
|
+
<Box>
|
|
114
|
+
<Button
|
|
115
|
+
disabled={loading}
|
|
116
|
+
startIcon={loading && <CircularProgress color="success" size={24} />}
|
|
117
|
+
onClick={handleLogin}
|
|
118
|
+
sx={{ width: '280px' }} variant="contained">Sign In</Button>
|
|
119
|
+
</Box>
|
|
120
|
+
<Box ml={1}>
|
|
121
|
+
<Button
|
|
122
|
+
onClick={handleJoin}
|
|
123
|
+
variant="outlined">Sign Up</Button>
|
|
124
|
+
</Box>
|
|
125
|
+
</Box> */}
|
|
126
|
+
<Box>
|
|
127
|
+
<div ref={divRef} />
|
|
128
|
+
{/* <div id="g_id_onload"
|
|
129
|
+
|
|
130
|
+
data-client_id="886217236574-52ccfmm2nj12dc63lcfb0ksms2akkimm.apps.googleusercontent.com"
|
|
131
|
+
data-context="signin"
|
|
132
|
+
data-ux_mode="redirect"
|
|
133
|
+
data-login_uri={`${api_host}/google/login/callback`}
|
|
134
|
+
data-itp_support="true">
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div class="g_id_signin"
|
|
138
|
+
data-type="standard"
|
|
139
|
+
data-shape="pill"
|
|
140
|
+
data-theme="filled_black"
|
|
141
|
+
data-text="signin_with"
|
|
142
|
+
data-size="large"
|
|
143
|
+
data-logo_alignment="left">
|
|
144
|
+
</div> */}
|
|
145
|
+
</Box>
|
|
146
|
+
</Box>
|
|
147
|
+
|
|
148
|
+
</Box>
|
|
149
|
+
</form>
|
|
150
|
+
</Box>
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export default LoginPage;
|