mori-admin-ui 1.0.0
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/index.html +13 -0
- package/package.json +21 -0
- package/src/App.tsx +58 -0
- package/src/components/BotStatusSection.tsx +20 -0
- package/src/components/ConnectionSection.tsx +52 -0
- package/src/components/CrawlerSection.tsx +45 -0
- package/src/index.tsx +15 -0
- package/src/services/api.ts +99 -0
- package/src/styles/index.css +44 -0
- package/src/types/global.d.ts +26 -0
- package/tsconfig.json +26 -0
- package/tsconfig.node.json +11 -0
- package/vite.config.ts +17 -0
package/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Mori Admin UI</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="mori-admin-ui-root"></div>
|
|
10
|
+
<script type="module" src="/src/index.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
13
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mori-admin-ui",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"react": "^18.2.0",
|
|
7
|
+
"react-dom": "^18.2.0"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@types/react": "^18.2.0",
|
|
11
|
+
"@types/react-dom": "^18.2.0",
|
|
12
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
13
|
+
"typescript": "^5.2.0",
|
|
14
|
+
"vite": "^5.0.0"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"dev": "vite",
|
|
18
|
+
"build": "tsc && vite build",
|
|
19
|
+
"preview": "vite preview"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/App.tsx
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { api } from './services/api';
|
|
3
|
+
import { ConnectionSection } from './components/ConnectionSection';
|
|
4
|
+
import { CrawlerSection } from './components/CrawlerSection';
|
|
5
|
+
import { BotStatusSection } from './components/BotStatusSection';
|
|
6
|
+
|
|
7
|
+
interface StatusState {
|
|
8
|
+
success: boolean;
|
|
9
|
+
isConnected?: boolean;
|
|
10
|
+
siteUrl?: string;
|
|
11
|
+
woocommerceVersion?: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function LoadingSpinner() {
|
|
15
|
+
return <div>Loading...</div>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function Header() {
|
|
19
|
+
return (
|
|
20
|
+
<header>
|
|
21
|
+
<h1>Mori Chatbot Setup</h1>
|
|
22
|
+
</header>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function App() {
|
|
27
|
+
const [status, setStatus] = useState<StatusState | null>(null);
|
|
28
|
+
const [loading, setLoading] = useState(true);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
// Fetch initial status
|
|
32
|
+
api.getStatus()
|
|
33
|
+
.then(data => {
|
|
34
|
+
setStatus(data);
|
|
35
|
+
})
|
|
36
|
+
.catch(err => {
|
|
37
|
+
console.error("Failed to fetch status", err);
|
|
38
|
+
})
|
|
39
|
+
.finally(() => setLoading(false));
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
if (loading) return <LoadingSpinner />;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="mori-admin-ui">
|
|
46
|
+
<Header />
|
|
47
|
+
{!status?.isConnected ? (
|
|
48
|
+
<ConnectionSection onConnect={() => setStatus(prev => prev ? ({...prev, isConnected: true}) : { success: true, isConnected: true })} />
|
|
49
|
+
) : (
|
|
50
|
+
<>
|
|
51
|
+
<CrawlerSection />
|
|
52
|
+
<BotStatusSection />
|
|
53
|
+
</>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { api } from '../services/api';
|
|
3
|
+
|
|
4
|
+
export function BotStatusSection() {
|
|
5
|
+
const [status, setStatus] = useState<any>(null);
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
api.getBotStatus().then(res => {
|
|
9
|
+
if (res.success) setStatus(res.data);
|
|
10
|
+
});
|
|
11
|
+
}, []);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className="bot-status-section">
|
|
15
|
+
<h2>Bot Status</h2>
|
|
16
|
+
<p>{status ? JSON.stringify(status) : 'Checking status...'}</p>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { api } from '../services/api';
|
|
3
|
+
|
|
4
|
+
interface ConnectionSectionProps {
|
|
5
|
+
onConnect: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function ConnectionSection({ onConnect }: ConnectionSectionProps) {
|
|
9
|
+
const [integrationId, setIntegrationId] = useState('');
|
|
10
|
+
const [loading, setLoading] = useState(false);
|
|
11
|
+
const [error, setError] = useState<string | null>(null);
|
|
12
|
+
|
|
13
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
14
|
+
e.preventDefault();
|
|
15
|
+
setLoading(true);
|
|
16
|
+
setError(null);
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const result = await api.connectStore(integrationId);
|
|
20
|
+
if (result.success) {
|
|
21
|
+
onConnect();
|
|
22
|
+
} else {
|
|
23
|
+
setError(result.message || 'Connection failed');
|
|
24
|
+
}
|
|
25
|
+
} catch (err: any) {
|
|
26
|
+
setError(err.message || 'An error occurred');
|
|
27
|
+
} finally {
|
|
28
|
+
setLoading(false);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="connection-section">
|
|
34
|
+
<h2>Connect Your Store</h2>
|
|
35
|
+
<p>Enter your integration ID to connect to Mori.</p>
|
|
36
|
+
<form onSubmit={handleSubmit}>
|
|
37
|
+
<input
|
|
38
|
+
type="text"
|
|
39
|
+
value={integrationId}
|
|
40
|
+
onChange={(e) => setIntegrationId(e.target.value)}
|
|
41
|
+
placeholder="client_id,client_secret"
|
|
42
|
+
required
|
|
43
|
+
/>
|
|
44
|
+
<button type="submit" disabled={loading}>
|
|
45
|
+
{loading ? 'Connecting...' : 'Connect to Mori'}
|
|
46
|
+
</button>
|
|
47
|
+
{error && <div className="error">{error}</div>}
|
|
48
|
+
</form>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { api } from '../services/api';
|
|
3
|
+
|
|
4
|
+
export function CrawlerSection() {
|
|
5
|
+
const [status, setStatus] = useState<any>(null);
|
|
6
|
+
const [loading, setLoading] = useState(false);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
loadStatus();
|
|
10
|
+
}, []);
|
|
11
|
+
|
|
12
|
+
const loadStatus = async () => {
|
|
13
|
+
try {
|
|
14
|
+
const res = await api.getCrawlStatus();
|
|
15
|
+
if (res.success) {
|
|
16
|
+
setStatus(res.data);
|
|
17
|
+
}
|
|
18
|
+
} catch (e) {
|
|
19
|
+
console.error(e);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const handleStartCrawl = async () => {
|
|
24
|
+
setLoading(true);
|
|
25
|
+
try {
|
|
26
|
+
await api.startCrawl();
|
|
27
|
+
await loadStatus();
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.error(e);
|
|
30
|
+
} finally {
|
|
31
|
+
setLoading(false);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="crawler-section">
|
|
37
|
+
<h2>Product Crawler</h2>
|
|
38
|
+
<p>Status: {status ? JSON.stringify(status) : 'Unknown'}</p>
|
|
39
|
+
<button onClick={handleStartCrawl} disabled={loading}>
|
|
40
|
+
{loading ? 'Starting...' : 'Start Crawl'}
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import ReactDOM from 'react-dom/client'
|
|
3
|
+
import App from './App'
|
|
4
|
+
import './styles/index.css'
|
|
5
|
+
|
|
6
|
+
const rootElement = document.getElementById('mori-admin-ui-root');
|
|
7
|
+
|
|
8
|
+
if (rootElement) {
|
|
9
|
+
ReactDOM.createRoot(rootElement).render(
|
|
10
|
+
<React.StrictMode>
|
|
11
|
+
<App />
|
|
12
|
+
</React.StrictMode>,
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Service to interact with WordPress backend
|
|
2
|
+
|
|
3
|
+
interface ApiResponse<T = any> {
|
|
4
|
+
success: boolean;
|
|
5
|
+
data?: T;
|
|
6
|
+
message?: string;
|
|
7
|
+
// Specific fields that might return on root level of response depending on endpoint
|
|
8
|
+
isConnected?: boolean;
|
|
9
|
+
siteUrl?: string;
|
|
10
|
+
woocommerceVersion?: string | null;
|
|
11
|
+
settings?: {
|
|
12
|
+
integrationId: string;
|
|
13
|
+
sharingId: string;
|
|
14
|
+
clientId: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ConnectResponse extends ApiResponse {
|
|
19
|
+
data?: {
|
|
20
|
+
is_new_keys: boolean;
|
|
21
|
+
sharing_id: string;
|
|
22
|
+
connection_verified: boolean;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Helper to get headers
|
|
27
|
+
const getHeaders = (): HeadersInit => {
|
|
28
|
+
return {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
'X-WP-Nonce': window.moriAdminUIData?.nonce || '',
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Base URL for API
|
|
35
|
+
const getApiUrl = (endpoint: string): string => {
|
|
36
|
+
const siteUrl = window.moriAdminUIData?.siteUrl || '';
|
|
37
|
+
return `${siteUrl}/wp-json/dori/v1${endpoint}`;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const api = {
|
|
41
|
+
connectStore: async (integrationId: string): Promise<ConnectResponse> => {
|
|
42
|
+
// Using REST API endpoint consistent with class-admin-ui-api.php
|
|
43
|
+
const response = await fetch(getApiUrl('/connect-store'), {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: getHeaders(),
|
|
46
|
+
body: JSON.stringify({ integrationId }),
|
|
47
|
+
});
|
|
48
|
+
return response.json();
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
getStatus: async (): Promise<ApiResponse> => {
|
|
52
|
+
const response = await fetch(getApiUrl('/status'), {
|
|
53
|
+
method: 'GET',
|
|
54
|
+
headers: getHeaders(),
|
|
55
|
+
});
|
|
56
|
+
return response.json();
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
getSettings: async (): Promise<ApiResponse> => {
|
|
60
|
+
const response = await fetch(getApiUrl('/settings'), {
|
|
61
|
+
method: 'GET',
|
|
62
|
+
headers: getHeaders(),
|
|
63
|
+
});
|
|
64
|
+
return response.json();
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
startCrawl: async (): Promise<ApiResponse> => {
|
|
68
|
+
const response = await fetch(getApiUrl('/start-crawl'), {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: getHeaders(),
|
|
71
|
+
});
|
|
72
|
+
return response.json();
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
getCrawlStatus: async (): Promise<ApiResponse> => {
|
|
76
|
+
const response = await fetch(getApiUrl('/crawl-status'), {
|
|
77
|
+
method: 'GET',
|
|
78
|
+
headers: getHeaders(),
|
|
79
|
+
});
|
|
80
|
+
return response.json();
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
createBot: async (): Promise<ApiResponse> => {
|
|
84
|
+
const response = await fetch(getApiUrl('/create-bot'), {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: getHeaders(),
|
|
87
|
+
});
|
|
88
|
+
return response.json();
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
getBotStatus: async (): Promise<ApiResponse> => {
|
|
92
|
+
const response = await fetch(getApiUrl('/bot-status'), {
|
|
93
|
+
method: 'GET',
|
|
94
|
+
headers: getHeaders(),
|
|
95
|
+
});
|
|
96
|
+
return response.json();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* Basic styles for the admin UI */
|
|
2
|
+
.mori-admin-ui {
|
|
3
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
4
|
+
max-width: 800px;
|
|
5
|
+
margin: 20px auto;
|
|
6
|
+
padding: 20px;
|
|
7
|
+
background: #fff;
|
|
8
|
+
border-radius: 8px;
|
|
9
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.mori-admin-ui h1 {
|
|
13
|
+
font-size: 24px;
|
|
14
|
+
margin-bottom: 20px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.mori-admin-ui input {
|
|
18
|
+
display: block;
|
|
19
|
+
width: 100%;
|
|
20
|
+
padding: 8px;
|
|
21
|
+
margin-bottom: 10px;
|
|
22
|
+
border: 1px solid #ccc;
|
|
23
|
+
border-radius: 4px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.mori-admin-ui button {
|
|
27
|
+
padding: 10px 20px;
|
|
28
|
+
background: #0073aa;
|
|
29
|
+
color: white;
|
|
30
|
+
border: none;
|
|
31
|
+
border-radius: 4px;
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.mori-admin-ui button:disabled {
|
|
36
|
+
background: #ccc;
|
|
37
|
+
cursor: not-allowed;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.error {
|
|
41
|
+
color: red;
|
|
42
|
+
margin-top: 10px;
|
|
43
|
+
}
|
|
44
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface MoriAdminUIData {
|
|
2
|
+
ajaxurl: string;
|
|
3
|
+
nonce: string;
|
|
4
|
+
siteUrl: string;
|
|
5
|
+
siteName: string;
|
|
6
|
+
isConnected: boolean;
|
|
7
|
+
woocommerceVersion: string | null;
|
|
8
|
+
wpVersion: string;
|
|
9
|
+
pluginVersion: string;
|
|
10
|
+
currentUser: {
|
|
11
|
+
id: number;
|
|
12
|
+
canManageOptions: boolean;
|
|
13
|
+
};
|
|
14
|
+
settings?: {
|
|
15
|
+
integrationId: string;
|
|
16
|
+
sharingId: string;
|
|
17
|
+
clientId: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declare global {
|
|
22
|
+
interface Window {
|
|
23
|
+
moriAdminUIData: MoriAdminUIData;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
/* Bundler mode */
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
|
|
17
|
+
/* Linting */
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"noFallthroughCasesInSwitch": true
|
|
22
|
+
},
|
|
23
|
+
"include": ["src"],
|
|
24
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
25
|
+
}
|
|
26
|
+
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
|
|
4
|
+
// https://vitejs.dev/config/
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react()],
|
|
7
|
+
build: {
|
|
8
|
+
rollupOptions: {
|
|
9
|
+
output: {
|
|
10
|
+
entryFileNames: `assets/[name].js`,
|
|
11
|
+
chunkFileNames: `assets/[name].js`,
|
|
12
|
+
assetFileNames: `assets/[name].[ext]`
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|