diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000000000000000000000000000000000000..b5195cc371ea3f20e4b97922cd8b823011ce836f
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,15 @@
+{
+ "presets": [
+ [
+ "@babel/preset-env",
+ {
+ "useBuiltIns": "usage",
+ "targets": {
+ "chrome": "49",
+ "ie": "11"
+ }
+ }
+ ],
+ "@babel/preset-react"
+ ]
+}
diff --git a/.gitignore b/.gitignore
index bca833d82ca5713b93b6e4d3f4c25d13b1560bf0..7f7cc510883a8996c8bda611a3628d9d07656ffa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
/coverage
# production
+/build
/dist
# misc
@@ -24,4 +25,4 @@ yarn-error.log*
node_modules
.eslintcache
package-lock.json
-folder.txt
+structure.txt
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..9dd2a5a6812235a424fef0b12db2abb356fbfa8a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2024, wuruipeng
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/app/general/common.js b/app/general/common.js
new file mode 100644
index 0000000000000000000000000000000000000000..4881978670cdee0cd6f6bbe89050e663940c0d97
--- /dev/null
+++ b/app/general/common.js
@@ -0,0 +1,39 @@
+/*
+ * @Author: wuruipeng
+ * @Date: 2024-04-08 09:57:24
+ * @LastEditors: wuruipeng
+ * @LastEditTime: 2024-04-10 17:01:17
+ * @description: 全局公共方法
+ */
+const { app, dialog } = require('electron')
+
+// 退出程序确定方法
+let willQuitApp = false // 前置判断条件,是否确认退出程序
+const confirmExit = event => {
+ if (!willQuitApp) {
+ event.preventDefault() // 取消默认行为
+ dialog
+ .showMessageBox({
+ type: 'warning',
+ title: 'Tips',
+ message: 'Are you sure quit this App ?',
+ buttons: ['OK', 'Cancel']
+ })
+ .then(result => {
+ const response = result.response
+ if (response === 0) {
+ willQuitApp = true
+ app.quit()
+ } else {
+ willQuitApp = false
+ }
+ })
+ .catch(() => {
+ willQuitApp = false
+ })
+ }
+}
+
+module.exports = {
+ confirmExit
+}
diff --git a/app/general/ipcEvent.js b/app/general/ipcEvent.js
new file mode 100644
index 0000000000000000000000000000000000000000..a2a88e27a84870e02def634987e5b62e8c71afad
--- /dev/null
+++ b/app/general/ipcEvent.js
@@ -0,0 +1,15 @@
+/*
+ * @Author: wuruipeng
+ * @Date: 2024-04-08 09:57:33
+ * @LastEditors: wuruipeng
+ * @LastEditTime: 2024-04-08 10:02:41
+ * @description: 监听渲染进程交互主进程方法
+ */
+
+const { app, ipcMain } = require('electron')
+
+// 关闭应用方法
+ipcMain.on('close-app', () => {
+ // 调用 app.quit() 方法来关闭应用程序
+ app.quit()
+})
diff --git a/app/main.js b/app/main.js
new file mode 100644
index 0000000000000000000000000000000000000000..6abb4b69033f417294fe5a84b27aa1d10b529269
--- /dev/null
+++ b/app/main.js
@@ -0,0 +1,123 @@
+/*
+ * @Author: wuruipeng
+ * @Date: 2024-03-06 17:39:06
+ * @LastEditors: wuruipeng
+ * @LastEditTime: 2024-04-11 14:47:50
+ * @description: 主进程配置
+ */
+
+const { app, screen, BrowserWindow, Menu, dialog } = require('electron')
+const path = require('path')
+const { confirmExit } = require('./general/common.js')
+require('./general/ipcEvent.js') // 配置监听ipc信息事件
+
+// 初始化主进程
+function initApp(rootPath) {
+ // 菜单模板
+ const template = [
+ {
+ label: 'Setting',
+ submenu: [
+ { label: 'Change Serve/Local', accelerator: 'CmdOrCtrl+S' },
+ { label: 'Toggle DevTools', role: 'toggleDevTools' },
+ { type: 'separator' },
+ { label: 'Quit', role: 'quit' }
+ ]
+ },
+ {
+ label: 'Window',
+ submenu: [
+ { label: 'Reload', role: 'reload' },
+ { label: 'Zoom In', role: 'zoomIn' },
+ { label: 'Zoom Out', role: 'zoomOut' },
+ { label: 'Toggle Fullscreen', role: 'togglefullscreen' }
+ ]
+ },
+ {
+ label: 'Help',
+ submenu: [{ label: `About ${app.name}`, role: 'about' }]
+ }
+ ]
+ // 基础窗口信息配置
+ const createWindow = function () {
+ const { width, height } = screen.getPrimaryDisplay().workAreaSize // 自适应默认尺寸
+ const mainWindow = new BrowserWindow({
+ width,
+ height,
+ x: 0,
+ y: 0,
+ webPreferences: {
+ devTools: true, // 打开开发者工具
+ nodeIntegration: true, // 使用nodejs整合模块
+ contextIsolation: true, // 上下文隔离
+ preload: path.join(__dirname, 'preload.js') // 设定html内预加载script文件
+ }
+ })
+ // 切换应用功能方法
+ const changePage = (type = null) => {
+ switch (type) {
+ case 'web':
+ mainWindow.loadURL('http://localhost:3000') // 在线功能加载 React 服务的 URL
+ // 需要启动服务的项目,同时监听加载失败事件
+ mainWindow.webContents.on(
+ 'did-fail-load',
+ (event, errorCode, errorDescription) => {
+ // 如果errorCode为-105(net::ERR_NAME_NOT_RESOLVED),则表示服务不可用
+ if (errorCode === -105) {
+ dialog.showErrorBox('Can not use this server')
+ } else {
+ dialog.showErrorBox(`Load Failed:${errorDescription}`)
+ }
+ }
+ )
+ break
+ default:
+ mainWindow.loadFile(
+ path.join(__dirname, '..', rootPath, 'index.html')
+ ) // 使用相对路径加载 HTML 文件
+ break
+ }
+ }
+ // 自定义应用切换事件
+ template[0].submenu[0].submenu = [
+ {
+ label: 'Online Web',
+ click: () => {
+ changePage('web')
+ }
+ },
+ {
+ label: 'Local Page',
+ click: () => {
+ changePage()
+ }
+ }
+ ]
+ // 定义并执行菜单配置
+ const menu = Menu.buildFromTemplate(template)
+ Menu.setApplicationMenu(menu)
+ changePage('web')
+ // 窗口关闭前进行确认验证
+ mainWindow.on('close', event => {
+ confirmExit(event)
+ })
+ }
+ // 应用加载完成配置
+ app.on('ready', createWindow)
+ app.on('activate', () => {
+ createWindow()
+ })
+ // 退出程序提示
+ app.on('before-quit', event => {
+ confirmExit(event)
+ })
+}
+
+try {
+ // 开发模式 配置electron主进程热更新,可支持css,js,html热更
+ require('electron-reloader')(module, {})
+ initApp('public')
+} catch (_) {
+ // 生产模式 electron-reloader依赖在打包后运行引用会造成读取不到的错误,从而定位到catch事件中,在catch中应去掉对该依赖的加载
+ initApp('build')
+}
diff --git a/app/preload.js b/app/preload.js
new file mode 100644
index 0000000000000000000000000000000000000000..8da3fdf96543808745e697251b98ab37e8ae067d
--- /dev/null
+++ b/app/preload.js
@@ -0,0 +1,29 @@
+/*
+ * @Author: wuruipeng
+ * @Date: 2024-04-08 09:55:51
+ * @LastEditors: wuruipeng
+ * @LastEditTime: 2024-04-08 16:06:34
+ * @description: 渲染进程预加载脚本(用于搭建传递主进程与渲染进程的属性使用和方法)
+ */
+
+const { contextBridge, ipcRenderer } = require('electron')
+const common = require('./general/common.js')
+
+// 可发送通信消息至主进程控制调用
+contextBridge.exposeInMainWorld('ipcRenderer', ipcRenderer)
+// 将需要暴露给渲染进程的 API 注册到一个受信任的预定义对象中(可返回更新值至渲染进程)
+contextBridge.exposeInMainWorld(
+ 'resultAPI', // 对象名称
+ {
+ // 定义一个 invoke 方法,用于从渲染进程向主进程发送消息,并等待主进程返回结果
+ invoke: (channel, data) => {
+ // 在这里使用 ipcRenderer 发送消息给主进程,并等待主进程返回结果
+ return ipcRenderer.invoke(channel, data)
+ },
+ // 监听主进程发送的消息
+ on: (channel, listener) => {
+ ipcRenderer.on(channel, listener)
+ }
+ }
+)
+contextBridge.exposeInMainWorld('common', common)
diff --git a/build/electron-builder.json b/build/electron-builder.json
deleted file mode 100644
index 3f9fab8ac68200fe09badbbaa6ae21c7d239d9f4..0000000000000000000000000000000000000000
--- a/build/electron-builder.json
+++ /dev/null
@@ -1,45 +0,0 @@
-{
- "appId": "com.example.myapp",
- "productName": "ElectronProject",
- "copyright": "Copyright © 2020-2024 WRP",
- "directories": {
- "output": "dist"
- },
- "files": [
- {
- "from": "build/",
- "to": "build/"
- },
- "main.js",
- "index.html",
- "package.json"
- ],
- "win": {
- "target": ["nsis"]
- },
- "mac": {
- "target": ["dmg"]
- },
- "linux": {
- "target": ["AppImage"]
- },
- "nsis": {
- "oneClick": false,
- "perMachine": true,
- "allowElevation": true,
- "allowToChangeInstallationDirectory": true,
- "contents": [
- {
- "x": 130,
- "y": 220,
- "type": "link",
- "path": "/Applications"
- },
- {
- "x": 410,
- "y": 220,
- "type": "file"
- }
- ]
- }
-}
diff --git a/node-folder.js b/config-structure.js
similarity index 82%
rename from node-folder.js
rename to config-structure.js
index 54c653e06a77a6c704349324680934f1af7c63f5..5b610726727bf8f6adfd2a6b58eb7da6ba49f633 100644
--- a/node-folder.js
+++ b/config-structure.js
@@ -1,3 +1,10 @@
+/*
+ * @Author: wuruipeng
+ * @Date: 2024-04-03 10:32:46
+ * @LastEditors: wuruipeng
+ * @LastEditTime: 2024-04-08 11:13:13
+ * @description:项目目录结构生成配置
+ */
const fs = require('fs')
const path = require('path')
@@ -56,13 +63,18 @@ function generateDirectoryText(
const folderPath = './'
// 指定要排除的文件夹列表
-const excludedFolders = ['.git', 'node_modules', '.eslintcache', 'folder.txt']
+const excludedFolders = [
+ '.git',
+ 'node_modules',
+ '.eslintcache',
+ 'structure.txt'
+]
const directoryText = generateDirectoryText(folderPath, '', excludedFolders)
// 指定要保存目录文本的文件路径
-const outputPath = 'folder.txt'
+const outputPath = 'structure.txt'
fs.writeFileSync(outputPath, directoryText)
-console.log(`目录信息已保存到 ${outputPath} 文件中。`)
+console.log(`Structure information has saved to ${outputPath}`)
diff --git a/index.html b/index.html
deleted file mode 100644
index 3d65494de087254c8afc3bd46b4f95f2b07d9e8c..0000000000000000000000000000000000000000
--- a/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
- Electron
-
-
- Electron
-
-
diff --git a/jest-preset.json b/jest-preset.json
new file mode 100644
index 0000000000000000000000000000000000000000..afddcf43c351f31cb0b2b84ceea027f4875d5265
--- /dev/null
+++ b/jest-preset.json
@@ -0,0 +1,6 @@
+{
+ "preset": "react",
+ "transformIgnorePatterns": [
+ "node_modules/(?!(@history/index|@api.*|@utils/index|@style.*)/)"
+ ]
+}
diff --git a/main.js b/main.js
deleted file mode 100644
index 76074d5cf316a2c20440292e03e0656f3b68b76c..0000000000000000000000000000000000000000
--- a/main.js
+++ /dev/null
@@ -1,15 +0,0 @@
-const { app, BrowserWindow } = require('electron')
-
-const createWindow = () => {
- const win = new BrowserWindow({
- width: 800,
- height: 600,
- webPreferences: {
- nodeIntegration: true
- }
- })
-
- win.loadFile('index.html')
-}
-
-app.whenReady().then(createWindow)
diff --git a/package.json b/package.json
index dbfef70c3bbf3c8d052202d02434d942fa4cfd21..c82ec47921ec0f0dd2d7ae8c841e973153ffc6fa 100644
--- a/package.json
+++ b/package.json
@@ -1,23 +1,125 @@
{
- "name": "electron-project",
+ "author": "WuRuipeng",
+ "license": "ISC",
+ "name": "mtlh-application",
"version": "1.0.0",
"description": "electron-project",
- "main": "main.js",
+ "main": "app/main.js",
+ "private": true,
+ "dependencies": {
+ "@testing-library/jest-dom": "^5.17.0",
+ "@testing-library/react": "^13.4.0",
+ "@testing-library/user-event": "^13.5.0",
+ "axios": "^1.6.5",
+ "fs-extra": "^11.1.1",
+ "lodash": "^4.17.21",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-redux": "^9.0.4",
+ "react-router-dom": "^6.21.1",
+ "redux": "^5.0.1",
+ "redux-thunk": "^3.1.0"
+ },
"scripts": {
- "start": "electron .",
+ "app": "electron .",
+ "serve": "webpack serve --mode development",
+ "build": "webpack --mode development",
+ "serve:build": "serve build",
"pack": "electron-builder",
- "structure": "node node-folder.js",
+ "pack:all": "npm run serve:build && npm run pack && serve build",
+ "jest": "jest",
+ "structure": "node config-structure.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://gitee.com/wuruipeng_admin/my-application-development.git"
},
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
"devDependencies": {
+ "@babel/core": "^7.22.9",
+ "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
+ "@babel/preset-env": "^7.23.8",
+ "@babel/preset-react": "^7.23.3",
+ "@types/react": "^18.2.79",
+ "@types/react-dom": "^18.2.25",
+ "babel-loader": "^9.1.3",
+ "copy-webpack-plugin": "^12.0.2",
+ "css-loader": "^3.6.0",
"electron": "^25.5.0",
- "electron-builder": "^24.6.3"
+ "electron-builder": "^24.6.3",
+ "electron-reloader": "^1.2.3",
+ "html-webpack-plugin": "^5.6.0",
+ "jest": "26.6.0",
+ "less": "^4.2.0",
+ "less-loader": "^12.2.0",
+ "style-loader": "^4.0.0",
+ "ts-loader": "^9.5.1",
+ "typescript": "^5.4.5",
+ "webpack": "^5.91.0",
+ "webpack-cli": "^5.1.4",
+ "webpack-dev-server": "^5.0.4"
+ },
+ "build": {
+ "asar": false,
+ "appId": "com.example.mtlh",
+ "productName": "MTLH",
+ "copyright": "Copyright © 2020-2024 WRP",
+ "icon": "build/logo256.png",
+ "directories": {
+ "output": "dist"
+ },
+ "extraResources": [
+ {
+ "from": "server/",
+ "to": "server/",
+ "filter": [
+ "**/*",
+ "!**/node_modules/**",
+ "!package-lock/*.json"
+ ]
+ }
+ ],
+ "files": [
+ "app/**/*",
+ "build/**/*",
+ "package.json"
+ ],
+ "win": {
+ "target": [
+ "nsis"
+ ]
+ },
+ "mac": {
+ "target": [
+ "dmg"
+ ]
+ },
+ "linux": {
+ "target": [
+ "AppImage"
+ ]
+ },
+ "nsis": {
+ "oneClick": false,
+ "perMachine": true,
+ "allowElevation": true,
+ "allowToChangeInstallationDirectory": true
+ },
+ "extends": null
},
- "keywords": [],
- "author": "",
- "license": "ISC"
+ "engines": {
+ "node": ">=16"
+ }
}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..cb1889536387249582de68204d830237100b4674
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+ Application
+
+
+
+
+
diff --git a/public/logo192.png b/public/logo192.png
new file mode 100644
index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9
Binary files /dev/null and b/public/logo192.png differ
diff --git a/public/logo256.png b/public/logo256.png
new file mode 100644
index 0000000000000000000000000000000000000000..41a1abd48b5da10e96a1793a452ddf090838d03b
Binary files /dev/null and b/public/logo256.png differ
diff --git a/public/manifest.json b/public/manifest.json
new file mode 100644
index 0000000000000000000000000000000000000000..ed99a00dd8bf8fec305ceb5ecd806656739a0c67
--- /dev/null
+++ b/public/manifest.json
@@ -0,0 +1,25 @@
+{
+ "short_name": "React App",
+ "name": "Create React App Sample",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ },
+ {
+ "src": "logo192.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "logo256.png",
+ "type": "image/png",
+ "sizes": "256x256"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e9e57dc4d41b9b46e05112e9f45b7ea6ac0ba15e
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:
diff --git a/server/index.js b/server/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..3962804f79e1ef159c02103dc3f434c4995ffd78
--- /dev/null
+++ b/server/index.js
@@ -0,0 +1,46 @@
+/*
+ * @Author: wuruipeng
+ * @Date: 2024-04-08 10:47:57
+ * @LastEditors: wuruipeng
+ * @LastEditTime: 2024-04-11 11:36:29
+ * @description:本地koa服务配置入口文件
+ */
+
+const Koa = require('koa')
+const app = new Koa()
+// 引入post请求中间件
+const bodyparser = require('koa-bodyparser')
+// 引入解决跨域组件
+const cors = require('koa2-cors')
+// 引入表单校验模块
+const koaBouncer = require('koa-bouncer')
+// 引入session模块
+const session = require('koa-session')
+
+// 使用post请求中间件
+app.use(bodyparser())
+// 使用解决跨域组件
+app.use(cors())
+// 使用表单校验模块
+app.use(koaBouncer.middleware())
+// 设置session签名,防止客户端修改(加密)
+app.keys = ['session secret', 'anthor secret']
+// 设置session配置项
+const SESSION_CONFIG = {
+ key: 'sid', // 设置cookie的key名
+ maxAge: 86400000, // 有效期,默认一天
+ httpOnly: true, // 仅服务端修改
+ signed: true // 签名cookie
+}
+// 使用session模块注册配置
+app.use(session(SESSION_CONFIG, app))
+
+// 中间件配置
+app.use(async ctx => {
+ // 获取访问次数
+ let n = ctx.session.count || 0
+ ctx.session.count = ++n // 存储对应session的信息
+ ctx.body += `第${n}次访问该网站`
+})
+
+app.listen('3001')
diff --git a/server/package.json b/server/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..6669ed75c1a9e1839bb2af109a6054dc1b1c664b
--- /dev/null
+++ b/server/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "server",
+ "version": "1.0.0",
+ "description": "Local NodeJs Server",
+ "main": "index.js",
+ "scripts": {
+ "serve": "node index.js",
+ "serve:nodemon": "nodemon index.js",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [
+ "koa",
+ "mongodb"
+ ],
+ "author": "WuRuipeng",
+ "license": "ISC",
+ "dependencies": {
+ "@koa/router": "^10.1.1",
+ "jsonwebtoken": "^8.5.1",
+ "koa": "^2.13.4",
+ "koa-bodyparser": "^4.3.0",
+ "koa-bouncer": "^6.0.0",
+ "koa-jwt": "^4.0.3",
+ "koa-logger": "^3.2.1",
+ "koa-multer": "^1.0.2",
+ "koa-onerror": "^4.2.0",
+ "koa-session": "^6.2.0",
+ "koa-static": "^5.0.0",
+ "koa2-cors": "^2.0.6",
+ "mongodb": "^4.4.0",
+ "mongoose": "^6.2.4",
+ "nodemon": "^3.1.0",
+ "sequelize": "^6.17.0",
+ "trek-captcha": "^0.4.0"
+ }
+}
diff --git a/src/index.tsx b/src/index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..21d01243aea03386d5da65c8cf4d14a3444fdc7e
--- /dev/null
+++ b/src/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react'
+import '@/styles/index.less'
+import { Root, createRoot } from 'react-dom/client'
+
+const rootElement = document.getElementById('root')
+if (rootElement) {
+ const root: Root = createRoot(rootElement)
+ root.render(
+
+ Main Theme
+
+ )
+} else {
+ console.error('Cannot find the rootElement!')
+}
diff --git a/src/setupTests.js b/src/setupTests.js
new file mode 100644
index 0000000000000000000000000000000000000000..8f2609b7b3e0e3897ab3bcaad13caf6876e48699
--- /dev/null
+++ b/src/setupTests.js
@@ -0,0 +1,5 @@
+// jest-dom adds custom jest matchers for asserting on DOM nodes.
+// allows you to do things like:
+// expect(element).toHaveTextContent(/react/i)
+// learn more: https://github.com/testing-library/jest-dom
+import '@testing-library/jest-dom';
diff --git a/src/styles/index.less b/src/styles/index.less
new file mode 100644
index 0000000000000000000000000000000000000000..bf6d0e8ef8ea1fb0f46fdd5f6bc0aa52aae159bb
--- /dev/null
+++ b/src/styles/index.less
@@ -0,0 +1,51 @@
+/* Typography */
+html,
+body,
+#root {
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+ font: inherit;
+ font-family: Arial, sans-serif;
+}
+
+/* Box sizing */
+*,
+*:before,
+*:after {
+ box-sizing: border-box;
+}
+
+/* Links */
+a {
+ color: #007bff;
+ text-decoration: none;
+ transition: color 0.3s ease;
+}
+
+a:hover {
+ color: #0056b3;
+}
+
+/* Lists */
+ul,
+ol {
+ margin: 0;
+ padding: 0;
+}
+
+/* Form elements */
+input,
+button,
+textarea,
+select {
+ font-family: inherit;
+ font-size: inherit;
+}
+
+/* Images */
+img {
+ max-width: 100%;
+ height: auto;
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..f0ccea136ca281099843bd4be24e1ebd8a2d855b
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,36 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "esnext",
+ "lib": ["dom", "dom.iterable", "es2015", "es2016", "es2017", "esnext"],
+ "strict": true,
+ "declaration": true,
+ "sourceMap": true,
+ "noEmit": false,
+ "outDir": "./build",
+ "rootDir": "./src",
+ "baseUrl": "./src",
+ "importHelpers": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "noImplicitAny": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "strictNullChecks": true,
+ "preserveConstEnums": true,
+ "resolveJsonModule": true,
+ "typeRoots": ["./node_modules/@types"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "moduleResolution": "node",
+ "isolatedModules": true,
+ "jsx": "react"
+ },
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
+ "exclude": ["app", "node_modules", "build", "dist", "public", "server"],
+ "paths": {
+ "@/*": ["src/*"]
+ }
+}
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..fadbdb625bc34eaaeef5403113ba8ef573b1a209
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,86 @@
+const path = require('path')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const CopyWebpackPlugin = require('copy-webpack-plugin')
+
+module.exports = {
+ mode: 'development',
+ entry: './src/index.tsx',
+ devServer: {
+ static: {
+ directory: path.join(__dirname, 'public')
+ },
+ compress: true,
+ port: 3000,
+ open: false,
+ allowedHosts: 'auto',
+ client: {
+ logging: 'info',
+ overlay: true,
+ progress: true
+ },
+ proxy: [
+ {
+ context: ['/api'],
+ target: 'http://localhost:3001',
+ secure: false,
+ changeOrigin: true,
+ pathRewrite: { '^/api': '' }
+ }
+ ]
+ },
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, 'src/')
+ },
+ extensions: ['.tsx', '.ts', '.jsx', '.js']
+ },
+ module: {
+ rules: [
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: 'babel-loader'
+ }
+ },
+ {
+ test: /\.(ts|tsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: 'ts-loader',
+ options: {
+ transpileOnly: false
+ }
+ }
+ },
+ {
+ test: /\.css$/,
+ use: ['style-loader', 'css-loader']
+ },
+ {
+ test: /\.less$/,
+ use: ['style-loader', 'css-loader', 'less-loader']
+ }
+ ]
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ template: './public/index.html'
+ }),
+ new CopyWebpackPlugin({
+ patterns: [
+ {
+ from: 'public',
+ to: '',
+ globOptions: {
+ ignore: ['**/index.html']
+ }
+ }
+ ]
+ })
+ ],
+ output: {
+ path: path.resolve(__dirname, 'build'),
+ filename: 'bundle.js'
+ }
+}