add FE source
This commit is contained in:
parent
a7e31b8ca9
commit
4a4d8e762c
10
.vscode/settings.json
vendored
Normal file
10
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "always",
|
||||
"source.fixAll": "always"
|
||||
},
|
||||
"editor.formatOnSaveMode": "modificationsIfAvailable",
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabSize": 2,
|
||||
"python.analysis.autoImportCompletions": true
|
||||
}
|
31
README.md
31
README.md
@ -37,3 +37,34 @@ generate migration auto code
|
||||
```bash
|
||||
alembic revision --autogenerate -m "version message"
|
||||
```
|
||||
|
||||
# Front-end
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
$ npm install # or pnpm install or yarn install
|
||||
```
|
||||
|
||||
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm run dev`
|
||||
|
||||
Runs the app in the development mode.<br>
|
||||
Open [http://localhost:5173](http://localhost:5173) to view it in the browser.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `dist` folder.<br>
|
||||
It correctly bundles Solid in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.<br>
|
||||
Your app is ready to be deployed!
|
||||
|
||||
## Deployment
|
||||
|
||||
Learn more about deploying your application with the [documentations](https://vitejs.dev/guide/static-deploy.html)
|
||||
|
@ -20,3 +20,8 @@ tasks:
|
||||
desc: runs the backend server
|
||||
cmds:
|
||||
- poetry run python fuware/app.py
|
||||
ui:dev:
|
||||
desc: runs the frontend server
|
||||
dir: fuware-fe
|
||||
cmds:
|
||||
- pnpm dev
|
||||
|
7
fuware-fe/.dockerignore
Normal file
7
fuware-fe/.dockerignore
Normal file
@ -0,0 +1,7 @@
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
node_modules
|
||||
README.md
|
||||
.git
|
||||
.gitignore
|
||||
.env
|
32
fuware-fe/.eslintrc.cjs
Normal file
32
fuware-fe/.eslintrc.cjs
Normal file
@ -0,0 +1,32 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
},
|
||||
plugins: ['solid'],
|
||||
extends: ['eslint:recommended', 'plugin:solid/recommended'],
|
||||
overrides: [
|
||||
{
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
files: ['.eslintrc.{js,cjs}'],
|
||||
parserOptions: {
|
||||
sourceType: 'script',
|
||||
},
|
||||
},
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
indent: ['warn', 2],
|
||||
quotes: ['error', 'single'],
|
||||
semi: ['error', 'never'],
|
||||
'max-lines-per-function': [1, 1000],
|
||||
'no-unused-vars': ['warn'],
|
||||
},
|
||||
}
|
24
fuware-fe/.gitignore
vendored
Normal file
24
fuware-fe/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
24
fuware-fe/.gitignore copy
Normal file
24
fuware-fe/.gitignore copy
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
3
fuware-fe/.lintstagedrc
Normal file
3
fuware-fe/.lintstagedrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"src/**/*.{js,jsx}": ["pnpm eslint", "pnpm prettier"]
|
||||
}
|
4
fuware-fe/.prettierignore
Normal file
4
fuware-fe/.prettierignore
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
/node_modules
|
||||
/public
|
||||
/build
|
7
fuware-fe/.prettierrc
Normal file
7
fuware-fe/.prettierrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
14
fuware-fe/Dockerfile
Normal file
14
fuware-fe/Dockerfile
Normal file
@ -0,0 +1,14 @@
|
||||
FROM node:18.19.0-alpine3.19
|
||||
|
||||
WORKDIR /app/client
|
||||
|
||||
COPY package*.json .
|
||||
RUN npm i
|
||||
|
||||
COPY . .
|
||||
ENV NODE_ENV=production
|
||||
ENV VITE_LOGIN_KEY=7fo24CMyIc
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
CMD ["npm", "run", "dev"]
|
17
fuware-fe/index copy.html
Normal file
17
fuware-fe/index copy.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>League of legend Skins</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.jsx"></script>
|
||||
</body>
|
||||
</html>
|
13
fuware-fe/index.html
Normal file
13
fuware-fe/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Solid</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.jsx"></script>
|
||||
</body>
|
||||
</html>
|
15
fuware-fe/jsconfig.json
Normal file
15
fuware-fe/jsconfig.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "./jsconfig.paths.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"target": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": false,
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
17
fuware-fe/jsconfig.paths.json
Normal file
17
fuware-fe/jsconfig.paths.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@api/*": ["src/api/*"],
|
||||
"@pages/*": ["src/pages/*"],
|
||||
"@components/*": ["src/components/*"],
|
||||
"@routes/*": ["src/routes/*"],
|
||||
"@utils/*": ["src/utils/*"],
|
||||
"@assets/*": ["src/assets/*"],
|
||||
"@context/*": ["src/context/*"],
|
||||
"@lang/*": ["src/lang/*"],
|
||||
"@hooks/*": ["src/hooks/*"],
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
}
|
||||
}
|
36
fuware-fe/package.json
Normal file
36
fuware-fe/package.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "fuware-fe",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||
"eslint": "eslint \"src/**/*.{js,jsx}\" --fix",
|
||||
"prettier": "prettier \"src/**/*.{js,jsx}\" --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hope-ui/solid": "^0.6.7",
|
||||
"@solidjs/router": "^0.13.3",
|
||||
"@stitches/core": "^1.2.8",
|
||||
"@tabler/icons-solidjs": "^3.3.0",
|
||||
"axios": "^1.6.8",
|
||||
"crypto-js": "^4.2.0",
|
||||
"solid-form-handler": "^1.2.3",
|
||||
"solid-js": "^1.8.15",
|
||||
"solid-styled-components": "^0.28.5",
|
||||
"yup": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^9.2.0",
|
||||
"eslint-plugin-solid": "^0.14.0",
|
||||
"lint-staged": "15.2.2",
|
||||
"prettier": "3.2.5",
|
||||
"vite": "^5.2.0",
|
||||
"vite-plugin-mkcert": "^1.17.5",
|
||||
"vite-plugin-solid": "^2.10.2"
|
||||
},
|
||||
"proxy": "http://localhost:9000"
|
||||
}
|
2787
fuware-fe/pnpm-lock.yaml
generated
Normal file
2787
fuware-fe/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
1
fuware-fe/public/vite.svg
Normal file
1
fuware-fe/public/vite.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
28
fuware-fe/src/App.css
Normal file
28
fuware-fe/src/App.css
Normal file
@ -0,0 +1,28 @@
|
||||
#root {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#main-page {
|
||||
height: calc(100svh - 56px);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#main-page.login-page {
|
||||
height: 100svh;
|
||||
}
|
||||
|
||||
#main-page .navbar {
|
||||
width: 350px;
|
||||
border-right: 1px solid #dedede;
|
||||
}
|
||||
|
||||
#main-page .main-content {
|
||||
width: calc(100vw - 350px);
|
||||
overflow-y: auto;
|
||||
}
|
15
fuware-fe/src/App.jsx
Normal file
15
fuware-fe/src/App.jsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { HopeProvider, NotificationsProvider } from '@hope-ui/solid'
|
||||
import './App.css'
|
||||
import { SiteContextProvider } from './context/SiteContext'
|
||||
|
||||
function App(props) {
|
||||
return (
|
||||
<HopeProvider>
|
||||
<NotificationsProvider>
|
||||
<SiteContextProvider>{props.children}</SiteContextProvider>
|
||||
</NotificationsProvider>
|
||||
</HopeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
10
fuware-fe/src/api/auth.js
Normal file
10
fuware-fe/src/api/auth.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { protocol } from './index'
|
||||
import { POST_LOGIN, POST_LOGOUT } from './url'
|
||||
|
||||
export const postLogin = (payload) => {
|
||||
return protocol.post(POST_LOGIN, payload)
|
||||
}
|
||||
|
||||
export const getLogout = () => {
|
||||
return protocol.get(POST_LOGOUT, {})
|
||||
}
|
6
fuware-fe/src/api/feature.js
Normal file
6
fuware-fe/src/api/feature.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { protocol } from './index'
|
||||
import { GET_DATE } from './url'
|
||||
|
||||
export const getWebData = (payload) => {
|
||||
return protocol.get(`${GET_DATE}?url=${payload}`, {})
|
||||
}
|
38
fuware-fe/src/api/index.js
Normal file
38
fuware-fe/src/api/index.js
Normal file
@ -0,0 +1,38 @@
|
||||
import axios from 'axios'
|
||||
import { Helpers } from '../utils/helper'
|
||||
|
||||
const protocol = axios.create({
|
||||
baseURL: '/',
|
||||
})
|
||||
|
||||
const forceLogout = () => {
|
||||
Helpers.clearCookie()
|
||||
window.location.href = '/login'
|
||||
}
|
||||
|
||||
protocol.interceptors.request.use(function (config) {
|
||||
config.headers.set(
|
||||
'Content-Type',
|
||||
config.headers.get('Content-Type') ?? 'application/json',
|
||||
)
|
||||
return config
|
||||
})
|
||||
|
||||
protocol.interceptors.response.use(
|
||||
(response) => {
|
||||
return response.data || {}
|
||||
},
|
||||
(error) => {
|
||||
const {
|
||||
response: { status, data },
|
||||
} = error
|
||||
|
||||
if (status === 403) {
|
||||
forceLogout()
|
||||
}
|
||||
|
||||
return Promise.reject(data)
|
||||
},
|
||||
)
|
||||
|
||||
export { protocol }
|
3
fuware-fe/src/api/url.js
Normal file
3
fuware-fe/src/api/url.js
Normal file
@ -0,0 +1,3 @@
|
||||
export const POST_LOGIN = '/api/auth/login'
|
||||
export const POST_LOGOUT = '/api/auth/logout'
|
||||
export const GET_DATE = '/api/user/get-data/'
|
1
fuware-fe/src/assets/solid.svg
Normal file
1
fuware-fe/src/assets/solid.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 166 155.3"><path d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z" fill="#76b3e1"/><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="27.5" y1="3" x2="152" y2="63.5"><stop offset=".1" stop-color="#76b3e1"/><stop offset=".3" stop-color="#dcf2fd"/><stop offset="1" stop-color="#76b3e1"/></linearGradient><path d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z" opacity=".3" fill="url(#a)"/><path d="M52 35l-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z" fill="#518ac8"/><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="95.8" y1="32.6" x2="74" y2="105.2"><stop offset="0" stop-color="#76b3e1"/><stop offset=".5" stop-color="#4377bb"/><stop offset="1" stop-color="#1f3b77"/></linearGradient><path d="M52 35l-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z" opacity=".3" fill="url(#b)"/><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="18.4" y1="64.2" x2="144.3" y2="149.8"><stop offset="0" stop-color="#315aa9"/><stop offset=".5" stop-color="#518ac8"/><stop offset="1" stop-color="#315aa9"/></linearGradient><path d="M134 80a45 45 0 00-48-15L24 85 4 120l112 19 20-36c4-7 3-15-2-23z" fill="url(#c)"/><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="75.2" y1="74.5" x2="24.4" y2="260.8"><stop offset="0" stop-color="#4377bb"/><stop offset=".5" stop-color="#1a336b"/><stop offset="1" stop-color="#1a336b"/></linearGradient><path d="M114 115a45 45 0 00-48-15L4 120s53 40 94 30l3-1c17-5 23-21 13-34z" fill="url(#d)"/></svg>
|
After Width: | Height: | Size: 1.6 KiB |
77
fuware-fe/src/components/Header.jsx
Normal file
77
fuware-fe/src/components/Header.jsx
Normal file
@ -0,0 +1,77 @@
|
||||
import { getLogout } from '@api/auth'
|
||||
import solidLogo from '@assets/solid.svg'
|
||||
import { useSiteContext } from '@context/SiteContext'
|
||||
import useLanguage from '@hooks/useLanguage'
|
||||
import {
|
||||
Avatar,
|
||||
AvatarBadge,
|
||||
Button,
|
||||
Flex,
|
||||
Spacer,
|
||||
Text,
|
||||
notificationService,
|
||||
} from '@hope-ui/solid'
|
||||
import { A, useNavigate } from '@solidjs/router'
|
||||
import { Helpers } from '@utils/helper'
|
||||
import { Show } from 'solid-js'
|
||||
import { css } from 'solid-styled-components'
|
||||
|
||||
export default function Header() {
|
||||
const { store, setAuth } = useSiteContext()
|
||||
const navigate = useNavigate()
|
||||
const language = useLanguage()
|
||||
|
||||
const logOut = async () => {
|
||||
try {
|
||||
await getLogout()
|
||||
Helpers.clearCookie()
|
||||
setAuth({ auth: false, user: null })
|
||||
navigate('/login', { replace: false })
|
||||
} catch (error) {
|
||||
notificationService.show({
|
||||
status: 'danger',
|
||||
title: 'Logout fail!',
|
||||
closable: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<header
|
||||
class={css`
|
||||
width: 100%;
|
||||
`}
|
||||
>
|
||||
<Flex bgColor="$success7" p="$3">
|
||||
<A href="/">
|
||||
<img
|
||||
src={solidLogo}
|
||||
class={css`
|
||||
width: 30px;
|
||||
`}
|
||||
alt="Solid logo"
|
||||
/>
|
||||
</A>
|
||||
<Spacer />
|
||||
<Flex alignItems="center">
|
||||
<Show when={store.auth}>
|
||||
<Avatar name={store.userInfo?.name} size="sm">
|
||||
<AvatarBadge boxSize="1.25em" bg="$success9" />
|
||||
</Avatar>
|
||||
<Text size="sm" ml="$2" mr="$5" color="white">
|
||||
{store.userInfo?.name}
|
||||
</Text>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="subtle"
|
||||
colorScheme="info"
|
||||
onClick={logOut}
|
||||
>
|
||||
{language.logout}
|
||||
</Button>
|
||||
</Show>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</header>
|
||||
)
|
||||
}
|
34
fuware-fe/src/components/Navbar/Navbar.jsx
Normal file
34
fuware-fe/src/components/Navbar/Navbar.jsx
Normal file
@ -0,0 +1,34 @@
|
||||
// import { styled } from 'solid-styled-components'
|
||||
|
||||
import useLanguage from '@hooks/useLanguage'
|
||||
import { Flex, Icon, Text } from '@hope-ui/solid'
|
||||
import { A } from '@solidjs/router'
|
||||
import { IconDashboard } from '@tabler/icons-solidjs'
|
||||
import { For } from 'solid-js'
|
||||
|
||||
const language = useLanguage('vi')
|
||||
|
||||
const NAVBAR_ITEM = [
|
||||
{
|
||||
path: '/dashboard',
|
||||
icon: IconDashboard,
|
||||
text: language?.dashboard,
|
||||
},
|
||||
]
|
||||
|
||||
export default function Navbar() {
|
||||
return (
|
||||
<div class="navbar">
|
||||
<For each={NAVBAR_ITEM}>
|
||||
{(item) => (
|
||||
<A href={item.path}>
|
||||
<Flex padding="$5" alignItems="center">
|
||||
<Icon as={item.icon} boxSize="$6" mr="$2_5" />
|
||||
<Text size="lg">{item.text}</Text>
|
||||
</Flex>
|
||||
</A>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
)
|
||||
}
|
1
fuware-fe/src/components/Navbar/index.js
Normal file
1
fuware-fe/src/components/Navbar/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './Navbar'
|
48
fuware-fe/src/context/SiteContext.jsx
Normal file
48
fuware-fe/src/context/SiteContext.jsx
Normal file
@ -0,0 +1,48 @@
|
||||
import { LOGIN_KEY, STORE_KEY } from '@utils/enum'
|
||||
import { Helpers } from '@utils/helper'
|
||||
import { createContext, onMount, useContext } from 'solid-js'
|
||||
import { createStore, produce } from 'solid-js/store'
|
||||
|
||||
export const SiteContext = createContext()
|
||||
|
||||
export function SiteContextProvider(props) {
|
||||
const [store, setStore] = createStore({
|
||||
auth: false,
|
||||
userInfo: null,
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
const checkCookie = Helpers.getCookie(LOGIN_KEY)
|
||||
if (checkCookie) {
|
||||
const storeData = Helpers.decrypt(localStorage.getItem(STORE_KEY))
|
||||
if (!storeData) return
|
||||
setStore(storeData)
|
||||
} else {
|
||||
localStorage.removeItem(STORE_KEY)
|
||||
}
|
||||
})
|
||||
|
||||
const setLocalStore = () => {
|
||||
localStorage.setItem(STORE_KEY, Helpers.encrypt(store))
|
||||
}
|
||||
|
||||
const setAuth = ({ auth, user }) => {
|
||||
setStore(
|
||||
produce((s) => {
|
||||
s.auth = auth
|
||||
s.userInfo = user
|
||||
}),
|
||||
)
|
||||
setLocalStore()
|
||||
}
|
||||
|
||||
return (
|
||||
<SiteContext.Provider value={{ store, setAuth }}>
|
||||
{props.children}
|
||||
</SiteContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useSiteContext() {
|
||||
return useContext(SiteContext)
|
||||
}
|
15
fuware-fe/src/hooks/useLanguage.js
Normal file
15
fuware-fe/src/hooks/useLanguage.js
Normal file
@ -0,0 +1,15 @@
|
||||
export default function useLanguage(selectLanguage = 'vi') {
|
||||
const data = import.meta.glob('@lang/*.json', {
|
||||
import: 'default',
|
||||
eager: true,
|
||||
})
|
||||
|
||||
const imp = {}
|
||||
|
||||
for (const path in data) {
|
||||
const keypath = path.match(/\/[a-zA-Z]+\./)[0].replace(/\/(\w+)\./, '$1')
|
||||
imp[keypath] = data[path]
|
||||
}
|
||||
|
||||
return imp[selectLanguage]
|
||||
}
|
31
fuware-fe/src/index.css
Normal file
31
fuware-fe/src/index.css
Normal file
@ -0,0 +1,31 @@
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(0, 0, 0, 1);
|
||||
background-color: #ffffff;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
30
fuware-fe/src/index.jsx
Normal file
30
fuware-fe/src/index.jsx
Normal file
@ -0,0 +1,30 @@
|
||||
import Layout from '@pages/Layout'
|
||||
import { Route, Router } from '@solidjs/router'
|
||||
import { For, lazy } from 'solid-js'
|
||||
import { render } from 'solid-js/web'
|
||||
import App from './App'
|
||||
import './index.css'
|
||||
import { ROUTES } from './routes'
|
||||
|
||||
const root = document.getElementById('root')
|
||||
|
||||
render(
|
||||
() => (
|
||||
<Router root={App}>
|
||||
<Route path="/login" component={lazy(() => import('@pages/Login'))} />
|
||||
<Route path="/" component={Layout}>
|
||||
<For each={ROUTES}>
|
||||
{(route) => (
|
||||
<Route
|
||||
path={route.path}
|
||||
component={route.components}
|
||||
matchFilters={route.filter}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</Route>
|
||||
<Route path="*" component={lazy(() => import('@pages/NotFound'))} />
|
||||
</Router>
|
||||
),
|
||||
root,
|
||||
)
|
4
fuware-fe/src/lang/en.json
Normal file
4
fuware-fe/src/lang/en.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"login": "Login",
|
||||
"logout": "Logout"
|
||||
}
|
10
fuware-fe/src/lang/vi.json
Normal file
10
fuware-fe/src/lang/vi.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"login": "Đăng Nhập",
|
||||
"logout": "Đăng xuất",
|
||||
"dashboard": "Bảng điều khiển",
|
||||
"champion": "Tướng",
|
||||
"skin": "Trang phục",
|
||||
"list": "Danh sách",
|
||||
"favourite": "Yêu thích",
|
||||
"bought": "Đã Mua"
|
||||
}
|
3
fuware-fe/src/pages/Dashboard.jsx
Normal file
3
fuware-fe/src/pages/Dashboard.jsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function Dashboard() {
|
||||
return <>Dashboard</>
|
||||
}
|
12
fuware-fe/src/pages/Home.jsx
Normal file
12
fuware-fe/src/pages/Home.jsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { useNavigate } from '@solidjs/router'
|
||||
import { onMount } from 'solid-js'
|
||||
|
||||
export default function Home() {
|
||||
const navigate = useNavigate()
|
||||
|
||||
onMount(() => {
|
||||
navigate('/dashboard', { replace: true })
|
||||
})
|
||||
|
||||
return <></>
|
||||
}
|
30
fuware-fe/src/pages/Layout.jsx
Normal file
30
fuware-fe/src/pages/Layout.jsx
Normal file
@ -0,0 +1,30 @@
|
||||
import Header from '@components/Header'
|
||||
import Navbar from '@components/Navbar'
|
||||
import { useSiteContext } from '@context/SiteContext'
|
||||
import { useNavigate } from '@solidjs/router'
|
||||
import { onMount, Show } from 'solid-js'
|
||||
|
||||
export default function Layout(props) {
|
||||
const { store } = useSiteContext()
|
||||
const navigate = useNavigate()
|
||||
|
||||
onMount(() => {
|
||||
if (!store.auth) {
|
||||
navigate('/login', { replace: true })
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<main>
|
||||
<Show when={store.auth}>
|
||||
<Header />
|
||||
</Show>
|
||||
<div id="main-page" class={store.auth ? '' : 'login-page'}>
|
||||
<Show when={store.auth}>
|
||||
<Navbar />
|
||||
</Show>
|
||||
<div class="main-content">{props.children}</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
141
fuware-fe/src/pages/Login.jsx
Normal file
141
fuware-fe/src/pages/Login.jsx
Normal file
@ -0,0 +1,141 @@
|
||||
import { postLogin } from '@api/auth'
|
||||
import { useSiteContext } from '@context/SiteContext'
|
||||
import useLanguage from '@hooks/useLanguage'
|
||||
import {
|
||||
Button,
|
||||
Center,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
FormLabel,
|
||||
Heading,
|
||||
Input,
|
||||
Stack,
|
||||
notificationService,
|
||||
} from '@hope-ui/solid'
|
||||
import { useNavigate } from '@solidjs/router'
|
||||
import { Field, useFormHandler } from 'solid-form-handler'
|
||||
import { yupSchema } from 'solid-form-handler/yup'
|
||||
import { Show, onMount } from 'solid-js'
|
||||
import { styled } from 'solid-styled-components'
|
||||
import * as yup from 'yup'
|
||||
|
||||
const LoginPage = styled('div')`
|
||||
width: 100%;
|
||||
height: 100svh;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
|
||||
.login-wrap {
|
||||
width: 500px;
|
||||
margin: 0 auto;
|
||||
|
||||
.login-box {
|
||||
box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 5px;
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const loginSchema = yup.object({
|
||||
username: yup.string().required('Username is required'),
|
||||
password: yup.string().required('Password is required'),
|
||||
})
|
||||
|
||||
const language = useLanguage()
|
||||
|
||||
export default function Login() {
|
||||
const { store, setAuth } = useSiteContext()
|
||||
const navigate = useNavigate()
|
||||
const formHandler = useFormHandler(yupSchema(loginSchema))
|
||||
const { formData } = formHandler
|
||||
|
||||
onMount(() => {
|
||||
if (store.auth) {
|
||||
navigate('/', { replace: true })
|
||||
}
|
||||
})
|
||||
|
||||
const submit = async (event) => {
|
||||
event.preventDefault()
|
||||
await formHandler.validateForm()
|
||||
try {
|
||||
const loginData = {
|
||||
username: formData()?.username,
|
||||
password: formData()?.password,
|
||||
}
|
||||
|
||||
const resp = await postLogin(loginData)
|
||||
|
||||
if (resp.status === 200) {
|
||||
const user = resp?.data || {}
|
||||
setAuth({ auth: true, user })
|
||||
formHandler.resetForm()
|
||||
navigate('/', { replace: true })
|
||||
}
|
||||
} catch (error) {
|
||||
notificationService.show({
|
||||
status: 'danger',
|
||||
title: 'Login fail!',
|
||||
description: error?.data || 'Your username or password input is wrong!',
|
||||
closable: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<LoginPage>
|
||||
<div class="login-wrap">
|
||||
<Center width="100%" mb="$10">
|
||||
gsdfgj;l
|
||||
</Center>
|
||||
<div className="login-box">
|
||||
<form autoComplete="off" onSubmit={submit}>
|
||||
<Stack direction="column">
|
||||
<Center w="100%" mb="$5">
|
||||
<Heading level="1" size="xl">
|
||||
{language.login}
|
||||
</Heading>
|
||||
</Center>
|
||||
<Field
|
||||
mode="input"
|
||||
name="username"
|
||||
formHandler={formHandler}
|
||||
render={(field) => (
|
||||
<FormControl mb="$3" invalid={field.helpers.error}>
|
||||
<FormLabel for="username">Username:</FormLabel>
|
||||
<Input id="username" type="text" {...field.props} />
|
||||
<Show when={field.helpers.error}>
|
||||
<FormErrorMessage>
|
||||
{field.helpers.errorMessage}
|
||||
</FormErrorMessage>
|
||||
</Show>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Field
|
||||
mode="input"
|
||||
name="password"
|
||||
formHandler={formHandler}
|
||||
render={(field) => (
|
||||
<FormControl mb="$5" invalid={field.helpers.error}>
|
||||
<FormLabel for="username">Password:</FormLabel>
|
||||
<Input id="password" type="password" {...field.props} />
|
||||
<Show when={field.helpers.error}>
|
||||
<FormErrorMessage>
|
||||
{field.helpers.errorMessage}
|
||||
</FormErrorMessage>
|
||||
</Show>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Button size="sm" type="submit">
|
||||
Login
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</LoginPage>
|
||||
)
|
||||
}
|
3
fuware-fe/src/pages/NotFound.jsx
Normal file
3
fuware-fe/src/pages/NotFound.jsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function NotFound() {
|
||||
return <>404</>
|
||||
}
|
1
fuware-fe/src/routes/index.js
Normal file
1
fuware-fe/src/routes/index.js
Normal file
@ -0,0 +1 @@
|
||||
export * from './routes'
|
21
fuware-fe/src/routes/routes.js
Normal file
21
fuware-fe/src/routes/routes.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { lazy } from 'solid-js'
|
||||
|
||||
export const ROUTES = [
|
||||
{
|
||||
path: '/',
|
||||
components: lazy(() => import('@pages/Home')),
|
||||
filter: {},
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
components: lazy(() => import('@pages/Dashboard')),
|
||||
filter: {},
|
||||
},
|
||||
// {
|
||||
// path: '/champions/:tab',
|
||||
// components: lazy(() => import('@pages/Champion')),
|
||||
// filter: {
|
||||
// tab: ['list', 'favourite'],
|
||||
// },
|
||||
// },
|
||||
]
|
3
fuware-fe/src/utils/enum.js
Normal file
3
fuware-fe/src/utils/enum.js
Normal file
@ -0,0 +1,3 @@
|
||||
export const SECRET_KEY = 'bGV0IGRvIGl0IGZvciBlbmNyeXRo'
|
||||
export const STORE_KEY = 'dXNlciBsb2dpbiBpbmZv'
|
||||
export const LOGIN_KEY = import.meta.env.VITE_LOGIN_KEY
|
42
fuware-fe/src/utils/helper.js
Normal file
42
fuware-fe/src/utils/helper.js
Normal file
@ -0,0 +1,42 @@
|
||||
import { AES, enc } from 'crypto-js'
|
||||
import { LOGIN_KEY, SECRET_KEY, STORE_KEY } from './enum'
|
||||
|
||||
export class Helpers {
|
||||
static getCookie = (cname) => {
|
||||
let name = cname + '='
|
||||
let ca = document.cookie.split(';')
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i]
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1)
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length)
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
static deleteCookie = (cname) => {
|
||||
document.cookie = `${cname}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`
|
||||
}
|
||||
|
||||
static clearCookie = () => {
|
||||
this.deleteCookie(LOGIN_KEY)
|
||||
localStorage.removeItem(STORE_KEY)
|
||||
}
|
||||
|
||||
static checkAuth = () => {
|
||||
return !!this.getCookie(LOGIN_KEY) && !!localStorage.getItem(STORE_KEY)
|
||||
}
|
||||
|
||||
static encrypt = (obj) => {
|
||||
return AES.encrypt(JSON.stringify(obj), SECRET_KEY).toString()
|
||||
}
|
||||
|
||||
static decrypt = (hash, defaultValue = {}) => {
|
||||
return hash
|
||||
? JSON.parse(AES.decrypt(hash, SECRET_KEY).toString(enc.Utf8))
|
||||
: defaultValue
|
||||
}
|
||||
}
|
73
fuware-fe/vite.config.js
Normal file
73
fuware-fe/vite.config.js
Normal file
@ -0,0 +1,73 @@
|
||||
import path, { dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import mkcert from 'vite-plugin-mkcert'
|
||||
import solid from 'vite-plugin-solid'
|
||||
|
||||
const _dirname = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
// production
|
||||
export default defineConfig(({ command, mode }) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
const env = loadEnv(mode, process.cwd(), '')
|
||||
|
||||
if (env.NODE_ENV === 'production') {
|
||||
return {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(_dirname, './src'),
|
||||
'@lang': path.resolve(_dirname, './src/lang'),
|
||||
'@api': path.resolve(_dirname, './src/api'),
|
||||
'@hooks': path.resolve(_dirname, './src/hooks'),
|
||||
'@pages': path.resolve(_dirname, './src/pages'),
|
||||
'@components': path.resolve(_dirname, './src/components'),
|
||||
'@routes': path.resolve(_dirname, './src/routes'),
|
||||
'@utils': path.resolve(_dirname, './src/utils'),
|
||||
'@assets': path.resolve(_dirname, './src/assets'),
|
||||
'@context': path.resolve(_dirname, './src/context'),
|
||||
},
|
||||
},
|
||||
plugins: [solid(), mkcert()],
|
||||
server: {
|
||||
https: true,
|
||||
host: true,
|
||||
port: 5001,
|
||||
strictPort: true,
|
||||
watch: {
|
||||
usePolling: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(_dirname, './src'),
|
||||
'@lang': path.resolve(_dirname, './src/lang'),
|
||||
'@api': path.resolve(_dirname, './src/api'),
|
||||
'@hooks': path.resolve(_dirname, './src/hooks'),
|
||||
'@pages': path.resolve(_dirname, './src/pages'),
|
||||
'@components': path.resolve(_dirname, './src/components'),
|
||||
'@routes': path.resolve(_dirname, './src/routes'),
|
||||
'@utils': path.resolve(_dirname, './src/utils'),
|
||||
'@assets': path.resolve(_dirname, './src/assets'),
|
||||
'@context': path.resolve(_dirname, './src/context'),
|
||||
},
|
||||
},
|
||||
plugins: [solid(), mkcert()],
|
||||
server: {
|
||||
https: true,
|
||||
host: true,
|
||||
port: 5001,
|
||||
strictPort: true,
|
||||
watch: {
|
||||
usePolling: true,
|
||||
},
|
||||
proxy: {
|
||||
'/api': 'http://localhost:9000',
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user