Done setup template

This commit is contained in:
2024-06-01 12:34:20 +00:00
parent 71d4afcc5e
commit 449a5f644f
104 changed files with 313 additions and 256 deletions

7
frontend/.dockerignore Normal file
View File

@ -0,0 +1,7 @@
Dockerfile
.dockerignore
node_modules
README.md
.git
.gitignore
.env

32
frontend/.eslintrc.cjs Normal file
View 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
frontend/.gitignore vendored Normal file
View 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
frontend/.lintstagedrc Normal file
View File

@ -0,0 +1,3 @@
{
"src/**/*.{js,jsx}": ["pnpm eslint", "pnpm prettier"]
}

4
frontend/.prettierignore Normal file
View File

@ -0,0 +1,4 @@
/node_modules
/public
/build

7
frontend/.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"trailingComma": "all",
"useTabs": false,
"tabWidth": 2,
"semi": false,
"singleQuote": true
}

14
frontend/Dockerfile Normal file
View 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
frontend/index.html Normal file
View 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>Fuware</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.jsx"></script>
</body>
</html>

16
frontend/jsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"extends": "./jsconfig.paths.json",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"resolveJsonModule": true,
"isolatedModules": false,
"allowJs": true,
"skipLibCheck": true
}
}

View 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/*"]
}
}
}

40
frontend/package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "fuware",
"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": {
"@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",
"solid-toast": "^0.5.0",
"yup": "^1.4.0"
},
"devDependencies": {
"autoprefixer": "^10.4.19",
"daisyui": "^4.11.1",
"eslint": "^8.56.0",
"eslint-plugin-solid": "^0.14.0",
"lint-staged": "15.2.2",
"postcss": "^8.4.38",
"prettier": "3.2.5",
"tailwindcss": "^3.4.3",
"vite": "^5.2.0",
"vite-plugin-mkcert": "^1.17.5",
"vite-plugin-solid": "^2.10.2"
},
"proxy": "http://localhost:9000"
}

3327
frontend/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 KiB

1
frontend/public/vite.svg Normal file
View 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

24
frontend/src/App.css Normal file
View File

@ -0,0 +1,24 @@
#root {
margin: 0 auto;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#main-page {
height: calc(100svh - 64px);
display: flex;
overflow: hidden;
}
#main-page.login-page {
height: 100svh;
}
#main-page .main-content {
max-height: calc(100svh - 64px);
overflow-y: auto;
}

23
frontend/src/App.jsx Normal file
View File

@ -0,0 +1,23 @@
import { MetaProvider } from '@solidjs/meta'
import { Toaster } from 'solid-toast'
import './App.css'
import { SiteContextProvider } from './context/SiteContext'
function App(props) {
return (
<MetaProvider>
<SiteContextProvider>
<Toaster
containerStyle={
props.location?.pathname.indexOf('/login') >= 0
? null
: { 'margin-top': '60px' }
}
/>
{props.children}
</SiteContextProvider>
</MetaProvider>
)
}
export default App

14
frontend/src/api/auth.js Normal file
View File

@ -0,0 +1,14 @@
import { protocol } from './index'
import { POST_LOGIN, POST_LOGOUT, POST_REFRESH } from './url'
export const postLogin = (payload) => {
return protocol.post(POST_LOGIN, payload)
}
export const getLogout = () => {
return protocol.get(POST_LOGOUT, {})
}
export const refreshToken = () => {
return protocol.get(POST_REFRESH, {})
}

80
frontend/src/api/index.js Normal file
View File

@ -0,0 +1,80 @@
import { LOGIN_KEY } from '@utils/enum'
import axios from 'axios'
import { Helpers } from '../utils/helper'
import { refreshToken } from './auth'
const protocol = axios.create({
baseURL: '/',
})
const forceLogout = () => {
Helpers.clearStorage()
window.location.href = '/login'
}
protocol.interceptors.request.use(async (config) => {
config.headers.set(
'Content-Type',
config.headers.get('Content-Type') ?? 'application/json',
)
if (
config.url.indexOf('/login') >= 0 ||
config.url.indexOf('/refresh') >= 0
) {
return config
}
const { accessToken, exp } = await JSON.parse(
Helpers.decrypt(localStorage.getItem(LOGIN_KEY)),
)
if (accessToken && !Helpers.checkTokenExpired(exp)) {
config.headers.set('Authorization', `Bearer ${accessToken}`)
}
return config
})
protocol.interceptors.response.use(
(response) => {
return response.data || {}
},
async (error) => {
const {
response: { status, data },
config,
} = error
if (
config.url.indexOf('/login') >= 0 ||
config.url.indexOf('/refresh') >= 0
) {
return Promise.reject(data)
}
if (status === 401 && !config._retry) {
config._retry = true
try {
// call refresh token
const resp = await refreshToken()
if (resp.status === 200) {
const { data } = resp
localStorage.setItem(LOGIN_KEY, Helpers.encrypt(JSON.stringify(data)))
config.headers['Authorization'] = `Bearer ${data.accessToken}`
return protocol(config)
}
} catch (error) {
forceLogout()
return Promise.reject(error)
}
}
if (status === 403) {
forceLogout()
}
return Promise.reject(data)
},
)
export { protocol }

4
frontend/src/api/url.js Normal file
View File

@ -0,0 +1,4 @@
export const POST_LOGIN = '/api/auth/login'
export const POST_LOGOUT = '/api/auth/logout'
export const POST_REFRESH = '/api/auth/refresh'
export const GET_USER_PROFILE = '/api/user/me'

6
frontend/src/api/user.js Normal file
View File

@ -0,0 +1,6 @@
import { protocol } from './index'
import { GET_USER_PROFILE } from './url'
export const getProfile = () => {
return protocol.get(GET_USER_PROFILE, {})
}

View File

@ -0,0 +1,585 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1835.000000pt" height="2012.000000pt" viewBox="0 0 1835.000000 2012.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,2012.000000) scale(0.100000,-0.100000)"
fill="#ffffff" stroke="none">
<path d="M17135 19631 c-3 -5 -20 -13 -38 -16 -17 -4 -36 -10 -42 -14 -5 -4
-30 -13 -55 -20 -25 -7 -53 -17 -62 -22 -10 -5 -25 -9 -33 -9 -9 0 -26 -6 -38
-14 -12 -8 -44 -18 -72 -21 -27 -4 -55 -11 -60 -16 -11 -8 -67 -27 -145 -49
-25 -6 -56 -18 -70 -24 -14 -7 -45 -18 -70 -25 -25 -7 -53 -17 -62 -22 -10 -5
-25 -9 -33 -9 -8 0 -23 -4 -33 -9 -47 -26 -87 -41 -109 -41 -12 0 -23 -4 -23
-10 0 -5 -16 -10 -35 -10 -19 0 -35 -4 -35 -10 0 -5 -5 -10 -11 -10 -6 0 -27
-7 -47 -16 -72 -31 -141 -54 -164 -54 -9 0 -21 -7 -28 -15 -7 -8 -20 -15 -30
-15 -19 0 -108 -33 -151 -56 -14 -7 -45 -16 -67 -20 -23 -4 -42 -10 -42 -14 0
-5 -15 -12 -32 -15 -18 -4 -37 -10 -43 -14 -5 -4 -28 -13 -50 -20 -22 -7 -49
-16 -60 -21 -11 -5 -38 -16 -60 -25 -22 -9 -48 -21 -57 -26 -10 -5 -25 -9 -33
-9 -8 0 -23 -4 -33 -9 -9 -5 -35 -17 -57 -26 -22 -10 -48 -22 -57 -26 -10 -5
-24 -9 -32 -9 -8 0 -36 -11 -63 -24 -26 -13 -75 -34 -108 -46 -33 -12 -78 -31
-101 -41 -22 -11 -48 -19 -57 -19 -9 0 -19 -4 -22 -10 -3 -5 -17 -10 -30 -10
-13 0 -26 -4 -30 -9 -8 -14 -65 -41 -85 -41 -20 0 -77 -27 -85 -41 -4 -5 -15
-9 -26 -9 -19 0 -28 -4 -76 -28 -13 -7 -29 -12 -37 -12 -7 0 -19 -7 -26 -15
-7 -8 -23 -15 -36 -15 -13 0 -24 -4 -24 -10 0 -5 -9 -10 -19 -10 -11 0 -23 -4
-26 -10 -3 -5 -16 -10 -27 -10 -12 0 -29 -6 -37 -13 -21 -18 -119 -57 -142
-57 -11 0 -19 -4 -19 -8 0 -11 -95 -52 -119 -52 -10 0 -24 -7 -31 -14 -6 -8
-28 -20 -48 -27 -20 -6 -41 -15 -47 -20 -5 -5 -21 -9 -36 -9 -15 0 -29 -7 -33
-15 -3 -8 -12 -15 -21 -15 -8 0 -24 -4 -35 -10 -11 -5 -29 -14 -40 -20 -11 -5
-29 -10 -39 -10 -11 0 -22 -4 -25 -9 -9 -14 -107 -61 -127 -61 -9 0 -22 -7
-29 -15 -7 -8 -18 -15 -25 -15 -6 0 -27 -7 -46 -16 -52 -25 -122 -56 -144 -64
-11 -4 -45 -19 -76 -33 -31 -15 -70 -30 -87 -33 -18 -4 -32 -10 -32 -15 0 -5
-11 -9 -24 -9 -13 0 -26 -6 -29 -13 -3 -7 -24 -19 -48 -27 -24 -7 -48 -17 -54
-22 -5 -4 -19 -8 -32 -8 -13 0 -23 -4 -23 -9 0 -11 -57 -41 -77 -41 -12 0 -29
-7 -73 -31 -8 -5 -31 -16 -50 -25 -19 -8 -43 -20 -52 -25 -10 -5 -27 -9 -37
-9 -11 0 -23 -4 -26 -10 -5 -8 -45 -28 -100 -50 -11 -4 -50 -22 -86 -39 -37
-17 -74 -31 -84 -31 -10 0 -23 -6 -29 -14 -6 -8 -40 -24 -76 -36 -36 -12 -72
-28 -80 -34 -8 -7 -28 -18 -45 -25 -16 -7 -38 -17 -47 -22 -10 -5 -27 -9 -37
-9 -11 0 -23 -4 -26 -10 -5 -8 -43 -27 -100 -50 -22 -9 -88 -39 -207 -94 -43
-20 -83 -36 -90 -36 -7 0 -28 -9 -46 -20 -18 -11 -37 -20 -43 -20 -6 0 -17 -7
-25 -15 -8 -8 -31 -17 -52 -21 -20 -4 -44 -12 -52 -18 -8 -7 -26 -17 -40 -23
-14 -6 -59 -27 -101 -47 -42 -20 -84 -36 -93 -36 -9 0 -16 -4 -16 -10 0 -5
-11 -10 -25 -10 -14 0 -25 -3 -25 -7 0 -12 -59 -43 -80 -43 -10 0 -22 -4 -25
-10 -3 -5 -13 -10 -21 -10 -8 0 -31 -10 -52 -23 -20 -13 -86 -43 -147 -67 -60
-24 -117 -49 -125 -55 -8 -6 -28 -17 -45 -24 -16 -7 -38 -17 -47 -22 -10 -5
-27 -9 -37 -9 -11 0 -22 -4 -26 -9 -7 -13 -64 -41 -82 -41 -7 0 -13 -4 -13
-10 0 -5 -11 -10 -25 -10 -14 0 -25 -4 -25 -10 0 -5 -4 -10 -10 -10 -10 0
-153 -63 -200 -89 -46 -25 -62 -31 -79 -31 -11 0 -23 -4 -26 -10 -3 -5 -15
-10 -25 -10 -10 0 -20 -4 -22 -9 -3 -9 -159 -84 -198 -95 -39 -12 -89 -36 -95
-46 -3 -6 -15 -10 -26 -10 -10 0 -19 -4 -19 -10 0 -5 -11 -10 -25 -10 -14 0
-25 -4 -25 -10 0 -5 -6 -10 -12 -10 -7 0 -38 -12 -68 -26 -30 -15 -66 -31 -80
-37 -14 -6 -32 -16 -40 -23 -8 -7 -46 -22 -85 -35 -38 -13 -74 -29 -78 -36 -4
-7 -13 -13 -20 -13 -7 0 -39 -14 -72 -31 -33 -16 -89 -40 -125 -51 -36 -12
-69 -28 -73 -35 -4 -7 -17 -13 -27 -13 -11 0 -20 -4 -20 -10 0 -5 -7 -10 -15
-10 -14 0 -35 -9 -75 -32 -8 -4 -22 -11 -30 -14 -8 -3 -24 -9 -35 -14 -11 -4
-38 -17 -60 -28 -22 -11 -56 -28 -75 -37 -19 -9 -43 -21 -52 -26 -10 -5 -27
-9 -37 -9 -11 0 -23 -4 -26 -10 -3 -5 -23 -17 -43 -26 -20 -9 -48 -21 -62 -27
-14 -6 -31 -16 -38 -21 -19 -14 -113 -46 -134 -46 -10 0 -18 -4 -18 -10 0 -12
-97 -60 -122 -60 -10 0 -18 -4 -18 -8 0 -9 -70 -42 -88 -42 -13 0 -53 -20 -62
-31 -5 -6 -65 -33 -105 -48 -11 -5 -45 -20 -75 -35 -30 -14 -61 -26 -67 -26
-7 0 -13 -4 -13 -10 0 -5 -9 -10 -19 -10 -11 0 -22 -4 -26 -9 -8 -14 -65 -41
-85 -41 -9 0 -51 -18 -93 -40 -41 -22 -84 -40 -95 -40 -11 0 -22 -7 -26 -15
-3 -8 -12 -15 -21 -15 -8 0 -24 -4 -35 -10 -11 -5 -29 -14 -40 -20 -11 -5 -27
-10 -35 -10 -8 0 -15 -4 -15 -8 0 -5 -19 -16 -42 -26 -24 -9 -51 -21 -60 -27
-10 -5 -24 -9 -32 -9 -7 0 -19 -7 -26 -15 -7 -8 -21 -15 -31 -15 -11 0 -19 -4
-19 -10 0 -5 -11 -10 -25 -10 -14 0 -25 -4 -25 -10 0 -5 -9 -10 -20 -10 -11 0
-20 -4 -20 -8 0 -10 -71 -42 -93 -42 -9 0 -19 -4 -22 -10 -3 -5 -13 -10 -21
-10 -8 0 -32 -11 -53 -25 -22 -13 -52 -27 -68 -31 -15 -4 -35 -12 -43 -19 -8
-7 -33 -19 -55 -26 -22 -7 -44 -17 -49 -21 -6 -4 -18 -8 -27 -8 -10 0 -19 -4
-21 -8 -5 -12 -109 -62 -130 -62 -10 0 -18 -4 -18 -9 0 -5 -13 -12 -30 -16
-16 -4 -30 -11 -30 -16 0 -5 -11 -9 -25 -9 -14 0 -25 -3 -25 -7 0 -7 -94 -54
-145 -74 -11 -4 -54 -23 -96 -43 -42 -20 -82 -36 -88 -36 -6 0 -11 -4 -11 -10
0 -5 -5 -10 -10 -10 -6 0 -30 -9 -52 -20 -23 -11 -47 -20 -54 -20 -12 0 -73
-29 -84 -40 -14 -14 -52 -30 -70 -30 -11 0 -20 -4 -20 -10 0 -5 -11 -12 -25
-16 -14 -3 -25 -10 -25 -15 0 -5 -7 -9 -15 -9 -8 0 -23 -4 -33 -9 -9 -5 -44
-21 -77 -36 -33 -15 -77 -36 -98 -46 -22 -10 -45 -19 -52 -19 -7 0 -15 -7 -19
-15 -3 -8 -12 -15 -19 -15 -8 0 -27 -6 -43 -14 -16 -8 -51 -24 -79 -36 -27
-12 -77 -35 -110 -51 -33 -16 -66 -29 -73 -29 -6 0 -12 -4 -12 -10 0 -5 -6
-10 -13 -10 -18 0 -75 -28 -82 -41 -4 -5 -15 -9 -26 -9 -10 0 -19 -4 -19 -10
0 -5 -6 -10 -14 -10 -8 0 -38 -12 -68 -26 -29 -15 -64 -31 -78 -36 -34 -14
-47 -21 -103 -51 -26 -15 -54 -27 -62 -27 -7 0 -15 -3 -17 -7 -3 -8 -129 -73
-141 -73 -3 0 -23 -11 -43 -25 -20 -14 -47 -25 -60 -25 -13 0 -24 -4 -24 -9 0
-5 -8 -11 -17 -15 -10 -3 -25 -9 -33 -14 -37 -22 -61 -32 -74 -32 -8 0 -16 -3
-18 -7 -3 -8 -32 -22 -193 -98 -44 -20 -96 -45 -115 -55 -19 -11 -51 -26 -70
-35 -97 -46 -105 -52 -108 -73 -2 -12 -8 -22 -13 -22 -5 0 -9 -10 -9 -23 0
-13 -6 -32 -14 -42 -12 -17 -12 -23 0 -47 8 -15 14 -40 14 -57 0 -16 5 -33 10
-36 6 -3 10 -15 10 -26 0 -18 18 -58 31 -69 6 -5 22 -35 51 -90 7 -14 17 -28
23 -32 5 -4 19 -31 30 -61 21 -57 85 -137 109 -137 8 0 36 -21 62 -47 42 -41
53 -46 91 -45 42 0 103 20 103 34 0 9 59 38 77 38 7 0 13 5 13 10 0 6 11 10
25 10 14 0 25 4 25 10 0 5 26 21 58 35 31 14 64 30 72 35 8 6 38 22 65 36 28
14 73 38 100 54 28 16 56 29 63 29 7 1 28 12 46 26 18 14 39 25 47 26 8 0 28
9 44 19 52 32 88 50 102 50 7 0 13 5 13 10 0 6 9 10 19 10 11 0 22 4 26 9 7
13 64 41 82 41 7 0 13 5 13 10 0 6 9 10 19 10 11 0 22 4 25 9 3 5 59 34 123
66 65 31 127 61 138 67 62 34 115 58 129 58 9 0 16 4 16 9 0 5 24 19 53 31 56
24 213 97 237 111 8 4 40 20 70 34 30 15 66 33 80 41 52 31 114 55 127 50 11
-5 13 -34 9 -153 -3 -82 -11 -337 -18 -568 -7 -240 -17 -426 -23 -435 -5 -8
-13 -109 -17 -225 -4 -115 -12 -313 -18 -440 -6 -126 -14 -327 -18 -445 -4
-141 -11 -223 -19 -237 -9 -15 -13 -82 -13 -230 0 -121 -4 -213 -10 -219 -5
-5 -12 -108 -14 -229 -11 -449 -21 -680 -31 -695 -10 -15 -20 -254 -31 -690
-2 -124 -9 -229 -14 -234 -6 -6 -10 -87 -10 -187 0 -148 -3 -179 -16 -190 -28
-23 -196 -109 -214 -109 -6 0 -10 -4 -10 -8 0 -5 -28 -21 -62 -37 -35 -16 -79
-39 -98 -51 -19 -11 -52 -26 -72 -33 -21 -7 -38 -16 -38 -19 0 -6 -52 -34 -70
-38 -11 -2 -57 -26 -85 -43 -16 -11 -38 -20 -47 -20 -10 -1 -18 -5 -18 -11 0
-5 -9 -10 -19 -10 -11 0 -22 -4 -26 -9 -7 -13 -64 -41 -82 -41 -7 0 -13 -4
-13 -10 0 -5 -9 -10 -20 -10 -11 0 -23 -7 -26 -15 -4 -8 -15 -15 -25 -15 -10
0 -19 -4 -19 -10 0 -5 -7 -10 -15 -10 -8 0 -36 -12 -62 -27 -53 -29 -64 -34
-108 -54 -16 -7 -37 -19 -45 -25 -8 -7 -18 -13 -22 -14 -14 -2 -83 -30 -98
-40 -14 -9 -50 -26 -110 -53 -14 -7 -39 -22 -56 -34 -17 -13 -36 -23 -42 -23
-6 0 -33 -12 -59 -27 -26 -15 -66 -36 -88 -47 -22 -12 -42 -23 -45 -26 -3 -3
-41 -24 -85 -46 -44 -22 -89 -46 -100 -52 -45 -25 -191 -92 -202 -92 -6 0 -13
-4 -15 -8 -4 -11 -108 -62 -125 -62 -7 0 -13 -4 -13 -10 0 -5 -11 -12 -25 -16
-14 -3 -25 -10 -25 -15 0 -5 -9 -9 -19 -9 -11 0 -23 -4 -26 -10 -3 -5 -14 -10
-25 -10 -10 0 -20 -7 -24 -15 -3 -8 -11 -15 -18 -15 -7 0 -24 -9 -38 -20 -14
-11 -31 -20 -39 -20 -8 0 -28 -10 -45 -23 -31 -22 -53 -34 -113 -59 -18 -7
-33 -16 -33 -20 0 -3 -17 -12 -37 -18 -21 -7 -55 -23 -75 -36 -21 -13 -42 -24
-47 -24 -10 0 -62 -28 -153 -81 -27 -16 -55 -29 -63 -29 -8 0 -15 -4 -15 -9 0
-11 -56 -41 -77 -41 -7 0 -13 -4 -13 -10 0 -5 -9 -10 -20 -10 -11 0 -23 -7
-26 -15 -4 -8 -12 -15 -20 -15 -7 0 -29 -9 -49 -20 -20 -11 -42 -20 -50 -20
-8 0 -15 -4 -15 -9 0 -6 -21 -21 -47 -34 -100 -51 -129 -66 -163 -82 -64 -30
-140 -74 -156 -90 -8 -8 -22 -15 -30 -15 -8 0 -14 -4 -14 -10 0 -5 -6 -10 -13
-10 -18 0 -75 -28 -82 -41 -4 -5 -15 -9 -25 -9 -10 0 -20 -4 -22 -9 -6 -16
-165 -101 -188 -101 -6 0 -10 -4 -10 -9 0 -5 -24 -21 -52 -35 -29 -15 -71 -38
-93 -52 -22 -13 -50 -29 -62 -34 -13 -5 -23 -14 -23 -20 0 -5 -7 -10 -15 -10
-17 0 -76 -32 -120 -66 -18 -13 -39 -24 -47 -24 -8 0 -23 -9 -33 -20 -9 -10
-40 -31 -69 -46 -28 -14 -55 -33 -59 -40 -4 -8 -14 -14 -22 -14 -8 0 -30 -13
-48 -30 -19 -16 -39 -30 -43 -30 -21 -1 -64 -51 -64 -74 0 -14 -5 -28 -10 -31
-13 -8 -13 -132 0 -140 6 -3 10 -22 10 -41 0 -19 4 -34 9 -34 11 0 41 -56 41
-76 0 -7 11 -21 25 -32 14 -11 25 -24 25 -29 0 -11 42 -97 57 -115 6 -7 16
-29 22 -49 13 -40 81 -118 121 -139 27 -14 59 -41 93 -77 39 -43 129 -21 194
45 10 10 38 30 63 43 25 13 54 32 65 42 11 10 38 27 60 37 22 10 49 28 59 39
11 12 25 21 33 21 16 0 50 18 78 42 11 9 52 33 90 53 39 20 74 40 80 46 5 5
17 9 27 9 10 0 18 4 18 9 0 11 56 41 77 41 7 0 13 5 13 10 0 6 5 10 11 10 6 0
25 11 43 25 18 14 40 25 49 25 10 0 17 5 17 10 0 6 9 10 20 10 11 0 20 4 20
10 0 5 27 22 60 37 33 14 60 30 60 35 0 4 5 8 12 8 6 0 19 6 27 13 9 8 32 20
51 27 19 8 37 17 40 21 3 3 30 19 60 33 30 15 78 40 105 56 28 16 58 29 68 29
9 1 17 5 17 10 0 11 56 41 77 41 7 0 13 5 13 10 0 6 11 10 24 10 13 0 26 7 30
15 3 8 31 27 62 42 86 42 94 47 94 55 0 5 7 8 15 8 8 0 36 12 62 27 54 30 64
35 113 58 35 17 114 59 203 108 26 15 54 27 62 27 8 0 15 3 15 8 0 10 58 42
76 42 8 0 14 5 14 10 0 6 11 10 25 10 14 0 25 4 25 9 0 4 39 29 88 53 154 80
202 106 215 116 6 5 23 13 37 17 14 4 43 19 64 33 22 13 44 25 50 27 17 3 58
23 91 44 17 11 36 20 43 20 7 1 29 12 50 26 39 26 145 83 252 136 36 18 67 36
68 41 2 4 10 8 18 8 7 0 29 9 49 20 20 11 42 20 49 20 8 0 16 6 19 14 3 7 18
17 34 21 15 3 53 22 84 41 31 19 64 34 73 34 9 0 16 4 16 9 0 5 8 11 18 15 9
3 31 14 47 25 17 11 38 20 48 20 9 1 17 5 17 10 0 5 8 11 18 15 9 3 24 9 32
14 38 22 61 32 75 32 8 0 15 4 15 9 0 5 8 11 18 15 9 3 24 10 32 15 8 5 40 21
70 36 30 15 82 42 115 60 106 59 124 67 134 60 18 -11 14 -450 -4 -503 -11
-32 -15 -92 -14 -257 0 -118 -3 -221 -8 -227 -8 -12 -16 -244 -28 -733 -3
-115 -10 -240 -15 -277 -6 -37 -10 -148 -10 -247 0 -99 -4 -224 -10 -278 -5
-54 -12 -217 -15 -363 -3 -146 -9 -429 -13 -630 -4 -214 -11 -371 -17 -380 -6
-9 -13 -190 -18 -450 -38 -2158 -40 -2240 -54 -2273 -9 -19 -13 -86 -13 -209
0 -161 2 -182 19 -204 18 -22 19 -23 35 -5 12 13 16 37 16 94 0 42 5 79 10 82
6 4 10 54 10 119 0 61 4 116 9 121 5 6 11 61 13 123 2 73 7 115 15 120 7 5 14
48 17 112 14 259 18 306 27 315 5 5 9 48 9 95 0 56 5 92 14 105 9 14 16 72 21
190 5 112 12 179 21 197 8 16 14 58 14 106 0 43 4 82 9 87 5 6 12 53 15 105 4
52 9 137 12 189 3 57 11 103 20 115 9 14 14 48 14 108 0 48 5 90 10 93 6 3 10
48 10 100 0 52 4 97 10 100 6 3 10 50 10 105 0 55 4 102 10 105 6 3 10 42 10
87 0 49 6 92 15 111 8 18 17 77 20 132 3 55 8 145 11 200 3 55 10 104 15 110
5 5 9 50 9 100 0 65 4 97 15 111 10 14 14 47 15 121 0 57 4 105 10 108 6 3 10
52 10 110 0 58 4 107 10 110 6 3 10 52 10 110 0 76 4 107 14 118 14 14 15 30
32 357 3 58 9 109 15 115 5 5 9 53 9 106 0 60 5 109 14 130 10 23 16 101 21
244 4 116 11 215 15 220 4 6 10 60 12 121 2 65 8 117 15 125 6 8 14 63 17 124
3 61 10 166 16 235 5 69 13 180 17 247 5 75 13 127 21 135 8 8 12 49 12 128 0
64 4 120 9 125 6 6 12 62 15 125 14 307 20 353 46 391 14 22 39 42 60 49 19 7
46 21 59 32 14 10 43 28 65 38 23 11 64 33 91 49 28 16 57 30 66 30 9 1 19 8
23 16 3 8 12 15 20 15 9 0 31 9 51 20 20 11 42 20 50 20 8 0 15 4 15 10 0 5
26 21 58 36 31 14 59 29 62 32 11 15 89 52 109 52 11 0 21 4 21 10 0 10 95 60
114 60 6 0 22 12 35 25 13 14 32 25 42 25 10 0 19 3 19 8 0 8 18 18 145 79 17
7 44 22 60 33 17 10 38 19 48 19 9 1 17 4 17 9 0 8 7 12 90 55 30 16 78 41
105 57 28 15 60 29 73 29 12 1 22 5 22 10 0 11 87 61 106 61 8 0 14 4 14 9 0
10 97 61 117 61 7 0 13 3 13 8 0 8 57 42 71 42 5 0 32 15 61 33 29 18 79 46
110 61 32 15 60 31 63 36 3 5 20 12 38 15 17 4 34 11 37 15 10 14 90 60 104
60 8 0 16 4 18 8 5 14 97 62 118 62 11 0 20 4 20 10 0 5 19 19 43 30 23 11 60
31 82 44 22 14 78 44 125 68 47 23 103 54 125 67 60 38 86 51 100 51 7 0 15 4
17 8 3 9 107 62 120 62 5 0 19 9 31 20 25 24 74 50 94 50 7 0 13 3 13 8 0 8
11 14 77 45 21 10 45 23 53 28 140 85 152 89 180 64 11 -10 20 -24 20 -32 0
-8 11 -26 25 -41 14 -15 25 -32 25 -39 0 -7 5 -13 10 -13 6 0 10 -9 10 -19 0
-11 3 -21 8 -23 12 -5 62 -87 62 -102 0 -8 3 -16 8 -18 8 -3 62 -84 62 -94 0
-3 15 -29 34 -57 18 -29 44 -71 56 -94 13 -24 27 -43 32 -43 4 0 8 -11 8 -25
0 -14 5 -25 10 -25 6 0 18 -16 27 -35 9 -19 20 -35 25 -35 4 0 8 -7 8 -15 0
-8 16 -37 35 -64 19 -26 35 -52 35 -55 0 -4 15 -31 33 -59 18 -29 41 -68 51
-87 10 -19 26 -44 36 -55 9 -11 24 -36 33 -56 9 -20 23 -42 32 -49 8 -7 15
-21 15 -31 0 -11 5 -19 10 -19 6 0 10 -6 10 -14 0 -8 11 -28 25 -44 14 -17 25
-37 25 -46 0 -9 5 -16 10 -16 6 0 10 -6 10 -13 0 -7 11 -24 25 -39 14 -15 25
-31 25 -36 0 -15 30 -69 45 -82 18 -15 45 -68 45 -87 0 -7 7 -16 16 -20 8 -3
26 -30 40 -59 13 -29 28 -55 33 -59 12 -7 41 -64 41 -80 0 -7 7 -15 15 -19 8
-3 26 -29 39 -58 14 -30 34 -62 45 -72 12 -11 21 -27 21 -38 0 -10 5 -18 10
-18 6 0 10 -9 10 -19 0 -10 6 -21 13 -24 13 -5 57 -81 57 -99 0 -6 11 -19 25
-30 14 -11 25 -27 26 -36 0 -9 8 -29 17 -43 29 -44 56 -87 77 -121 11 -18 28
-44 37 -58 9 -14 17 -35 17 -47 1 -13 6 -23 12 -23 16 0 38 -31 39 -52 0 -10
5 -18 10 -18 6 0 10 -7 10 -16 0 -9 11 -25 25 -36 14 -11 25 -26 25 -34 0 -17
18 -50 43 -78 21 -25 47 -74 47 -91 0 -7 7 -15 15 -19 8 -3 26 -30 40 -60 14
-30 30 -58 36 -62 6 -3 20 -25 30 -48 10 -23 27 -51 39 -61 11 -10 20 -26 20
-37 0 -10 5 -18 10 -18 6 0 10 -8 10 -18 0 -10 11 -29 25 -42 14 -13 25 -32
25 -42 0 -10 5 -18 10 -18 6 0 10 -6 10 -13 0 -7 11 -24 25 -39 14 -15 25 -32
25 -38 0 -6 15 -32 32 -58 18 -26 44 -67 57 -92 13 -25 27 -47 31 -50 6 -5 21
-31 52 -90 7 -14 15 -27 18 -30 4 -3 12 -20 19 -37 8 -18 18 -33 23 -33 5 0
18 -20 29 -45 11 -24 25 -50 32 -57 19 -20 47 -66 47 -78 0 -6 8 -20 18 -30
10 -11 29 -40 41 -65 12 -25 28 -51 34 -58 19 -20 36 -49 48 -80 6 -15 15 -27
19 -27 4 0 13 -12 19 -28 17 -41 45 -86 59 -95 6 -4 12 -15 12 -23 0 -8 9 -24
20 -36 11 -12 20 -30 20 -40 0 -10 4 -18 9 -18 9 0 35 -39 74 -110 10 -19 28
-44 38 -56 10 -11 19 -26 19 -32 0 -7 16 -35 35 -62 19 -27 35 -55 35 -60 0
-6 8 -20 18 -30 10 -11 28 -40 41 -65 12 -24 26 -47 31 -50 11 -7 40 -64 40
-79 0 -6 11 -20 25 -30 14 -10 25 -27 25 -37 0 -11 5 -19 10 -19 6 0 10 -6 10
-14 0 -7 11 -25 25 -40 14 -15 25 -36 25 -47 0 -10 4 -19 8 -19 4 0 16 -15 27
-32 10 -18 21 -35 24 -38 4 -3 12 -16 19 -30 7 -14 21 -38 32 -55 35 -54 49
-80 62 -108 7 -15 16 -27 20 -27 5 0 19 -24 33 -53 13 -30 31 -60 40 -67 9 -7
24 -32 35 -54 10 -23 28 -50 39 -60 12 -11 21 -27 21 -37 0 -10 5 -21 10 -24
15 -10 60 -82 60 -97 0 -7 11 -22 25 -32 14 -10 25 -25 25 -33 0 -13 65 -121
90 -150 5 -7 10 -19 10 -28 0 -8 4 -15 9 -15 10 0 61 -86 61 -102 0 -6 11 -19
25 -30 14 -11 25 -28 25 -38 0 -10 3 -20 8 -22 13 -6 62 -76 62 -89 0 -7 10
-23 21 -35 24 -26 42 -55 61 -96 7 -16 16 -28 20 -28 4 0 21 -26 37 -58 16
-32 36 -64 45 -71 9 -8 16 -19 16 -26 0 -7 11 -23 24 -37 12 -14 26 -32 29
-39 4 -8 18 -32 32 -54 14 -22 28 -47 32 -55 4 -8 15 -24 25 -34 10 -11 18
-26 18 -34 0 -8 11 -23 25 -34 14 -11 25 -27 25 -37 0 -10 11 -27 25 -39 14
-12 25 -28 25 -35 0 -8 9 -22 20 -32 11 -10 20 -23 20 -30 0 -6 11 -22 25 -35
14 -13 25 -29 25 -35 0 -7 11 -24 24 -38 14 -15 27 -36 31 -47 3 -11 11 -20
16 -20 5 0 9 -7 9 -15 0 -8 5 -15 11 -15 14 0 4 77 -13 97 -17 20 -58 105 -58
118 0 7 -7 18 -15 25 -8 7 -15 23 -15 36 0 12 -4 26 -10 29 -5 3 -10 15 -10
26 0 10 -4 19 -10 19 -5 0 -10 9 -10 20 0 11 -7 23 -15 26 -8 4 -15 15 -15 25
0 10 -4 19 -10 19 -5 0 -10 9 -10 19 0 11 -4 23 -10 26 -5 3 -10 14 -10 25 0
10 -7 20 -15 24 -8 3 -15 12 -15 21 0 8 -4 23 -10 33 -33 61 -81 170 -86 199
-3 18 -11 37 -18 41 -6 4 -17 21 -25 37 -7 17 -17 38 -22 47 -5 10 -9 25 -9
33 0 8 -4 15 -9 15 -11 0 -41 56 -41 77 0 7 -4 13 -10 13 -5 0 -10 11 -10 25
0 14 -4 25 -9 25 -11 0 -41 56 -41 77 0 7 -4 13 -10 13 -5 0 -10 9 -10 19 0
11 -4 22 -9 26 -6 3 -22 31 -36 63 -15 31 -31 64 -36 72 -18 30 -59 121 -59
130 0 6 -3 10 -8 10 -9 0 -52 93 -52 114 0 9 -4 16 -9 16 -11 0 -41 56 -41 77
0 7 -4 13 -10 13 -5 0 -10 11 -10 25 0 14 -4 25 -9 25 -11 0 -41 56 -41 77 0
7 -4 13 -10 13 -5 0 -10 9 -10 19 0 11 -4 23 -10 26 -5 3 -23 34 -40 68 -16
34 -51 98 -76 142 -25 44 -52 96 -60 115 -8 19 -19 42 -24 50 -23 37 -60 115
-60 126 0 7 -4 14 -8 16 -11 4 -62 108 -62 125 0 7 -4 13 -10 13 -5 0 -12 11
-16 25 -3 14 -10 25 -15 25 -5 0 -9 7 -9 15 0 14 -38 87 -70 135 -9 14 -24 41
-34 60 -9 19 -21 42 -26 50 -5 8 -21 41 -35 72 -14 31 -29 59 -34 63 -5 3 -13
20 -17 38 -3 17 -17 48 -30 67 -13 19 -24 40 -24 47 0 7 -4 13 -10 13 -5 0
-10 7 -10 17 0 9 -10 30 -23 47 -13 17 -36 57 -51 88 -15 32 -31 60 -37 63 -5
4 -9 15 -9 26 0 10 -4 19 -10 19 -5 0 -10 6 -10 13 0 14 -6 26 -70 142 -22 39
-51 95 -66 125 -15 30 -29 57 -33 60 -3 3 -12 19 -20 35 -7 17 -17 38 -22 47
-5 10 -9 25 -9 33 0 8 -4 15 -9 15 -11 0 -41 56 -41 76 0 7 -4 14 -9 16 -13 5
-61 86 -61 103 0 8 -4 15 -10 15 -5 0 -10 6 -10 14 0 7 -7 19 -15 26 -8 7 -15
21 -15 31 0 11 -4 19 -10 19 -5 0 -10 8 -11 18 0 9 -9 31 -19 47 -11 17 -31
53 -45 80 -15 28 -39 73 -55 100 -16 28 -29 58 -29 68 -1 10 -8 20 -16 23 -8
4 -15 10 -15 16 0 5 -9 28 -20 50 -11 23 -20 47 -20 54 0 6 -6 14 -12 17 -7 2
-26 29 -41 58 -16 30 -37 68 -47 84 -10 17 -19 38 -19 48 -1 9 -5 17 -11 17
-5 0 -10 8 -10 18 0 10 -5 23 -11 29 -6 6 -23 35 -38 64 -51 100 -92 174 -102
183 -5 6 -9 18 -9 28 0 10 -4 18 -8 18 -4 0 -14 16 -21 35 -7 19 -28 58 -47
86 -19 28 -34 57 -34 65 0 8 -4 14 -10 14 -5 0 -12 11 -16 25 -3 14 -10 25
-15 25 -5 0 -9 9 -9 19 0 11 -4 23 -10 26 -5 3 -10 14 -10 25 0 10 -7 20 -15
24 -8 3 -15 14 -15 25 0 11 -3 21 -7 23 -10 4 -33 45 -85 148 -23 47 -45 87
-50 88 -4 2 -8 15 -8 28 0 13 -4 24 -10 24 -5 0 -10 7 -10 15 0 8 -4 15 -8 15
-5 0 -23 28 -41 63 -18 34 -45 85 -61 112 -15 28 -29 58 -29 68 -1 9 -5 17
-10 17 -5 0 -14 12 -20 28 -16 38 -60 114 -77 133 -8 8 -14 20 -14 26 0 11
-74 156 -90 178 -20 27 -60 107 -60 120 0 8 -4 15 -10 15 -5 0 -12 11 -16 25
-3 14 -10 25 -15 25 -5 0 -9 6 -9 13 0 20 -28 73 -45 87 -9 7 -22 29 -31 49
-19 45 -24 54 -57 114 -15 26 -27 54 -27 62 0 8 -4 15 -9 15 -5 0 -21 24 -36
53 -15 28 -35 66 -45 82 -10 17 -19 38 -19 48 -1 9 -5 17 -11 17 -5 0 -10 7
-10 15 0 8 -16 39 -35 69 -19 30 -35 58 -35 61 0 4 -7 13 -15 22 -8 8 -13 19
-9 24 3 5 -2 12 -10 15 -9 3 -16 12 -16 20 0 13 -43 97 -63 122 -11 14 14 48
48 64 33 15 66 35 78 47 6 6 19 11 29 11 10 0 21 7 24 15 4 8 10 15 16 15 5 0
28 9 50 20 23 11 47 20 54 20 6 0 14 6 17 13 2 6 29 25 58 40 30 16 68 37 84
47 17 10 38 19 48 19 9 1 17 6 17 11 0 6 8 10 19 10 10 0 24 7 31 15 7 8 17
15 22 15 6 0 29 13 52 28 23 16 69 41 101 57 33 16 82 42 108 57 26 15 54 28
62 28 8 0 15 4 15 9 0 10 97 61 116 61 7 0 17 6 21 13 10 16 49 37 68 37 8 0
15 5 15 10 0 6 5 10 11 10 6 0 25 11 43 25 18 14 39 25 47 26 8 0 28 9 44 19
47 29 87 50 97 50 6 0 22 8 36 19 43 30 102 61 116 61 8 0 16 7 20 15 3 8 15
15 26 15 11 0 20 3 20 8 0 7 48 32 62 32 5 0 19 9 31 20 12 12 32 23 44 26 13
4 23 10 23 15 0 5 9 9 19 9 11 0 23 5 26 10 8 13 104 60 122 60 7 0 13 4 13 8
0 5 10 14 23 20 12 7 49 28 82 47 33 20 67 38 75 41 46 18 165 82 207 112 17
12 38 22 47 22 9 0 16 5 16 10 0 6 11 10 25 10 14 0 25 4 25 9 0 5 26 22 58
37 31 16 59 31 62 34 3 4 12 10 20 13 8 4 29 16 45 26 17 11 38 20 48 20 9 1
17 5 17 10 0 5 8 11 18 15 9 3 24 9 32 14 8 5 35 20 60 32 57 29 137 72 196
105 24 14 51 25 59 25 8 0 15 4 15 10 0 5 11 12 25 16 14 3 25 9 25 13 0 3 15
12 33 20 17 8 34 17 37 20 7 9 41 28 75 45 94 44 92 42 266 138 75 42 109 60
164 86 22 11 58 30 80 42 22 13 51 30 65 37 14 8 39 23 55 33 17 10 38 19 48
19 9 1 17 6 17 11 0 6 9 10 20 10 11 0 23 7 26 15 4 8 12 15 20 15 7 0 30 10
51 21 21 12 67 38 103 57 36 19 72 41 80 47 8 6 38 20 65 30 28 10 52 21 55
25 3 4 23 15 45 25 22 10 51 26 65 35 46 31 121 70 134 70 8 0 16 7 20 15 3 8
15 15 26 15 11 0 20 5 20 10 0 6 8 10 18 11 9 0 31 9 47 20 17 10 37 22 45 26
8 3 47 25 85 48 39 23 87 48 108 54 20 7 37 16 37 19 0 4 15 13 33 20 58 24
82 36 112 58 17 11 46 29 65 38 19 10 58 30 85 46 28 15 58 29 68 29 9 1 17 5
17 11 0 5 11 12 25 16 14 3 25 10 25 14 0 4 11 11 24 14 14 3 31 12 39 20 16
16 87 56 100 56 5 0 29 12 55 27 26 16 50 28 54 28 3 0 10 3 14 8 4 4 15 7 25
7 10 0 22 6 26 13 11 19 104 71 213 120 14 7 39 21 55 32 17 12 44 28 60 36
17 7 36 20 43 27 7 6 20 12 28 12 8 0 14 5 14 10 0 6 6 10 13 10 17 0 75 28
82 40 5 8 34 23 85 44 8 3 33 18 55 31 22 14 48 28 58 32 9 3 17 9 17 13 0 10
57 40 75 40 7 0 20 9 31 21 10 11 37 29 60 40 23 10 48 25 56 32 7 7 34 23 60
36 25 12 52 29 58 37 7 7 17 14 23 14 6 0 19 8 29 17 11 9 42 29 71 44 40 20
53 32 55 53 3 21 -1 26 -17 26 -12 0 -21 -4 -21 -10 0 -5 -17 -10 -38 -10 -21
0 -46 -6 -57 -14 -11 -7 -40 -16 -65 -19 -102 -14 -130 -20 -136 -28 -3 -5
-18 -9 -33 -9 -16 0 -34 -7 -41 -15 -7 -8 -27 -15 -46 -15 -18 0 -36 -4 -39
-10 -3 -5 -22 -10 -41 -10 -19 0 -34 -4 -34 -10 0 -5 -14 -10 -30 -10 -18 0
-33 -6 -36 -15 -4 -8 -19 -15 -34 -15 -16 0 -32 -4 -35 -10 -3 -5 -15 -10 -26
-10 -19 0 -27 -3 -86 -34 -17 -9 -40 -16 -51 -16 -12 0 -24 -4 -27 -10 -3 -5
-14 -10 -24 -10 -9 0 -55 -16 -101 -35 -47 -19 -102 -38 -122 -41 -21 -4 -43
-13 -49 -20 -6 -8 -22 -14 -35 -14 -13 0 -24 -4 -24 -10 0 -5 -11 -10 -25 -10
-23 0 -41 -7 -85 -32 -8 -4 -22 -11 -30 -14 -8 -3 -23 -10 -32 -15 -10 -5 -29
-9 -43 -9 -14 0 -25 -4 -25 -10 0 -5 -11 -10 -24 -10 -13 0 -26 -7 -30 -15 -3
-9 -18 -15 -36 -15 -16 0 -30 -4 -30 -10 0 -5 -11 -10 -25 -10 -14 0 -25 -4
-25 -10 0 -5 -11 -10 -24 -10 -13 0 -29 -7 -36 -15 -7 -8 -19 -15 -26 -15 -8
0 -22 -4 -32 -9 -9 -5 -35 -14 -57 -21 -22 -7 -47 -18 -55 -24 -8 -7 -42 -20
-75 -30 -33 -10 -67 -23 -75 -30 -8 -7 -33 -18 -55 -25 -46 -14 -83 -29 -105
-42 -8 -5 -22 -12 -30 -15 -8 -3 -26 -11 -40 -16 -14 -6 -36 -15 -50 -21 -14
-5 -37 -16 -52 -23 -14 -8 -33 -14 -42 -14 -9 0 -16 -4 -16 -10 0 -5 -7 -10
-15 -10 -8 0 -23 -4 -33 -9 -25 -13 -134 -62 -157 -71 -11 -4 -31 -12 -45 -18
-90 -39 -132 -58 -188 -83 -34 -16 -76 -32 -92 -36 -17 -3 -30 -9 -30 -12 0
-8 -52 -31 -115 -52 -22 -8 -44 -20 -48 -26 -4 -7 -17 -13 -27 -13 -11 0 -20
-4 -20 -10 0 -5 -11 -10 -25 -10 -14 0 -25 -4 -25 -10 0 -5 -13 -10 -29 -10
-16 0 -31 -6 -35 -15 -3 -8 -12 -15 -20 -15 -9 0 -31 -9 -51 -20 -20 -11 -42
-20 -50 -20 -8 0 -15 -3 -15 -7 0 -5 -21 -16 -48 -26 -26 -10 -84 -36 -128
-57 -45 -22 -88 -40 -95 -40 -8 0 -23 -7 -33 -15 -11 -8 -28 -15 -38 -15 -10
0 -18 -4 -18 -10 0 -5 -9 -10 -19 -10 -11 0 -23 -4 -26 -10 -3 -5 -15 -10 -26
-10 -10 0 -19 -4 -19 -9 0 -12 -58 -41 -82 -41 -10 0 -18 -4 -18 -10 0 -5 -13
-10 -30 -10 -16 0 -30 -3 -30 -8 0 -4 -17 -16 -37 -26 -21 -10 -46 -22 -55
-27 -10 -5 -25 -9 -33 -9 -8 0 -15 -4 -15 -9 0 -5 -15 -15 -32 -22 -18 -6 -46
-17 -63 -24 -67 -25 -115 -49 -115 -57 0 -4 -6 -8 -14 -8 -8 0 -49 -18 -92
-40 -43 -22 -84 -40 -91 -40 -7 0 -44 -16 -82 -35 -37 -19 -71 -35 -75 -35 -3
0 -24 -11 -46 -25 -22 -14 -46 -25 -55 -25 -8 0 -15 -4 -15 -10 0 -5 -11 -10
-24 -10 -14 0 -28 -4 -31 -10 -3 -5 -14 -10 -24 -10 -10 0 -24 -7 -31 -15 -7
-8 -21 -15 -31 -15 -11 0 -19 -4 -19 -10 0 -5 -8 -10 -17 -11 -10 0 -31 -9
-48 -19 -45 -28 -161 -82 -227 -106 -20 -7 -63 -26 -95 -42 -32 -16 -71 -36
-88 -43 -16 -8 -32 -17 -35 -20 -3 -3 -23 -14 -45 -24 -45 -19 -141 -63 -167
-76 -10 -5 -25 -9 -33 -9 -8 0 -15 -4 -15 -10 0 -5 -9 -10 -19 -10 -11 0 -22
-4 -25 -9 -8 -12 -106 -61 -122 -61 -7 0 -17 -6 -21 -13 -4 -8 -30 -22 -58
-32 -27 -10 -68 -28 -90 -40 -22 -12 -60 -28 -85 -36 -25 -7 -49 -19 -53 -26
-4 -7 -17 -13 -27 -13 -11 0 -20 -4 -20 -10 0 -5 -9 -10 -19 -10 -11 0 -23 -4
-26 -10 -3 -5 -14 -10 -25 -10 -10 0 -20 -7 -24 -15 -3 -8 -15 -15 -26 -15
-11 0 -20 -4 -20 -10 0 -5 -6 -10 -13 -10 -18 0 -75 -28 -82 -41 -4 -5 -15 -9
-26 -9 -10 0 -19 -4 -19 -10 0 -5 -7 -10 -15 -10 -8 0 -23 -4 -33 -9 -9 -5
-42 -20 -72 -34 -78 -35 -121 -56 -150 -73 -41 -23 -60 -18 -60 15 0 17 5 33
10 36 6 4 28 34 50 68 22 34 43 67 48 72 4 6 26 39 47 75 22 36 45 72 50 81 6
9 27 42 48 73 20 31 37 61 37 67 0 5 7 12 15 15 8 4 15 11 15 18 0 6 9 22 20
36 11 14 20 32 20 41 0 9 11 22 25 29 14 7 25 22 25 32 0 18 46 89 63 96 4 2
7 11 7 20 0 8 7 24 17 34 17 19 33 44 55 88 7 14 15 27 19 30 3 3 14 20 24 38
11 17 23 32 27 32 4 0 8 9 8 19 0 11 11 28 25 39 14 11 25 29 25 41 0 12 5 21
10 21 6 0 10 6 10 14 0 7 11 25 25 40 14 15 25 33 25 40 0 8 7 20 16 27 9 7
20 22 25 34 5 11 21 38 35 60 13 22 27 48 31 58 3 9 9 17 13 17 5 0 20 24 34
53 13 29 34 61 45 71 12 11 21 26 21 34 0 8 16 36 35 62 19 26 35 51 35 55 0
4 16 29 35 55 19 26 35 53 35 59 0 7 11 24 25 39 14 15 25 32 25 39 0 7 5 13
10 13 6 0 10 9 10 19 0 11 11 28 25 39 14 11 25 28 26 38 0 11 8 30 17 44 9
14 26 41 37 59 11 18 30 47 42 65 12 17 34 55 49 84 15 30 33 56 41 59 7 3 13
12 13 19 0 12 44 88 60 104 3 3 16 25 29 50 13 25 30 52 37 61 8 8 14 21 14
27 0 7 4 12 8 12 4 0 13 12 20 28 7 15 23 45 37 67 13 22 29 49 35 60 6 11 19
34 30 50 11 17 25 41 32 55 7 14 18 31 25 38 7 7 13 20 13 28 0 8 4 14 8 14 5
0 17 15 27 34 14 26 38 44 94 72 42 21 78 41 81 44 9 11 72 40 87 40 7 0 13 4
13 9 0 5 8 11 18 15 9 3 31 14 47 25 17 11 40 20 51 20 12 1 24 7 27 14 3 8
30 26 61 41 31 15 58 31 61 37 4 5 13 9 21 9 8 0 38 15 67 33 29 18 66 38 82
46 17 7 37 17 45 22 8 5 22 12 30 16 8 4 17 9 20 13 3 3 21 14 40 23 19 10 42
21 50 26 8 5 29 15 45 22 17 8 44 24 61 36 17 13 46 28 64 35 19 6 49 21 67
32 96 62 123 77 152 82 17 4 31 10 31 14 0 5 26 22 58 37 31 16 73 39 92 51
19 12 40 22 47 22 7 0 23 10 37 23 14 13 44 30 68 37 24 7 48 19 53 25 6 6 25
18 43 26 17 7 32 17 32 20 0 4 8 9 18 13 9 3 24 10 32 15 8 5 29 15 45 22 17
8 44 24 61 36 17 13 38 23 47 23 8 0 17 3 19 8 2 4 30 20 63 37 33 16 67 34
75 41 30 24 91 54 109 54 10 0 21 7 25 15 3 8 12 15 21 15 8 0 15 4 15 9 0 5
8 11 18 14 9 3 33 14 52 25 19 11 49 26 65 33 17 8 32 17 35 21 3 3 31 20 63
36 31 16 63 34 70 39 6 5 28 16 47 23 19 8 40 19 47 25 31 26 40 32 73 45 19
7 42 18 50 25 8 6 29 17 45 25 17 8 44 24 60 35 17 12 45 27 63 33 18 6 45 20
60 30 15 11 43 27 62 36 19 9 67 35 105 56 39 22 87 47 108 57 20 10 37 21 37
25 0 4 16 14 35 23 36 15 92 42 126 60 10 5 19 13 19 17 0 5 9 8 20 8 11 0 20
5 20 10 0 10 22 21 98 52 17 7 32 16 32 20 0 3 25 19 55 33 30 15 59 33 66 41
6 8 19 14 28 15 9 0 30 9 46 20 17 11 38 22 48 25 9 4 17 9 17 13 0 4 16 13
35 21 19 8 35 17 35 21 0 5 17 15 38 24 20 10 53 26 72 37 19 10 43 22 53 25
9 3 17 9 17 14 0 5 9 9 19 9 10 0 21 6 24 13 3 8 29 26 59 41 29 15 60 31 68
35 8 5 36 19 63 32 26 12 52 28 58 36 6 7 15 13 20 13 5 0 36 18 70 40 34 22
70 40 79 40 10 0 20 6 23 13 2 6 49 34 103 62 55 27 108 58 119 68 11 10 30
20 42 23 13 4 23 10 23 15 0 5 7 9 14 9 8 0 27 11 42 25 15 14 35 25 45 25 11
0 19 5 19 10 0 6 7 10 15 10 7 0 18 6 22 13 4 7 29 23 56 36 26 13 52 30 58
38 6 7 18 13 25 13 8 0 14 5 14 10 0 6 9 10 20 10 11 0 20 4 20 8 0 5 18 17
40 28 22 10 40 22 40 27 0 4 6 7 14 7 7 0 19 6 25 13 6 8 32 25 58 38 27 13
52 29 56 36 4 7 13 13 19 13 5 0 27 13 47 29 20 16 60 41 89 56 29 15 57 36
63 46 5 11 17 19 25 19 9 0 22 6 29 13 31 27 44 37 51 37 4 0 31 20 60 45 28
25 57 45 63 45 11 0 31 32 31 49 0 18 -90 13 -124 -8 -17 -10 -53 -24 -81 -31
-27 -7 -57 -19 -66 -27 -8 -7 -23 -13 -32 -13 -9 0 -19 -4 -22 -10 -3 -5 -17
-10 -29 -10 -13 0 -29 -7 -36 -15 -7 -8 -23 -15 -36 -15 -12 0 -26 -4 -29 -10
-3 -5 -17 -10 -29 -10 -13 0 -29 -7 -36 -15 -7 -8 -21 -15 -31 -15 -10 0 -21
-4 -24 -10 -3 -5 -17 -10 -29 -10 -13 0 -29 -7 -36 -15 -7 -8 -17 -15 -22 -15
-5 0 -28 -9 -50 -20 -23 -11 -47 -20 -54 -20 -6 0 -17 -7 -24 -15 -7 -8 -23
-15 -36 -15 -12 0 -26 -4 -29 -10 -3 -5 -17 -10 -30 -10 -12 0 -25 -7 -29 -15
-3 -8 -12 -15 -19 -15 -8 0 -44 -16 -81 -35 -37 -19 -73 -35 -80 -35 -7 0 -34
-11 -60 -25 -26 -14 -56 -25 -67 -25 -10 0 -19 -4 -19 -10 0 -5 -6 -10 -14
-10 -7 0 -19 -7 -26 -15 -7 -8 -23 -15 -36 -15 -13 0 -24 -4 -24 -10 0 -5 -11
-10 -24 -10 -13 0 -29 -7 -36 -15 -7 -8 -23 -15 -35 -15 -13 0 -25 -3 -27 -7
-5 -12 -111 -63 -131 -63 -9 0 -17 -4 -17 -10 0 -5 -6 -10 -14 -10 -17 0 -80
-32 -84 -42 -2 -5 -14 -8 -27 -8 -12 0 -28 -7 -35 -15 -7 -8 -21 -15 -31 -15
-10 0 -21 -4 -24 -10 -3 -5 -15 -10 -25 -10 -10 0 -20 -3 -22 -7 -5 -11 -67
-43 -84 -43 -16 0 -56 -20 -65 -34 -8 -10 -77 -36 -96 -36 -7 0 -24 -11 -39
-25 -15 -14 -34 -25 -43 -25 -10 0 -48 -16 -85 -35 -38 -19 -77 -35 -86 -35
-10 0 -20 -6 -23 -13 -3 -7 -23 -19 -46 -27 -23 -7 -46 -19 -52 -27 -6 -7 -22
-13 -35 -13 -13 0 -24 -3 -24 -7 0 -9 -48 -33 -66 -33 -6 0 -17 -7 -24 -15 -7
-8 -23 -15 -36 -15 -13 0 -24 -4 -24 -10 0 -5 -9 -10 -19 -10 -10 0 -21 -7
-25 -15 -3 -8 -14 -15 -25 -15 -19 0 -104 -42 -125 -61 -6 -5 -18 -9 -28 -9
-22 0 -60 -20 -71 -37 -4 -7 -19 -13 -32 -13 -13 0 -27 -4 -30 -10 -3 -5 -15
-10 -26 -10 -10 0 -19 -4 -19 -8 0 -5 -19 -16 -42 -26 -89 -37 -108 -47 -108
-56 0 -6 -11 -10 -24 -10 -14 0 -28 -4 -31 -10 -3 -5 -14 -10 -25 -10 -10 0
-20 -7 -24 -15 -3 -8 -14 -15 -25 -15 -11 0 -23 -4 -26 -10 -3 -5 -15 -10 -25
-10 -10 0 -20 -3 -22 -7 -3 -8 -33 -23 -110 -57 -20 -9 -40 -21 -43 -26 -3 -6
-17 -10 -31 -10 -13 0 -24 -4 -24 -10 0 -5 -7 -10 -16 -10 -8 0 -22 -7 -30
-15 -9 -8 -24 -15 -35 -15 -10 0 -19 -4 -19 -10 0 -5 -6 -10 -13 -10 -18 0
-75 -28 -82 -41 -4 -5 -17 -9 -31 -9 -13 0 -24 -4 -24 -10 0 -5 -9 -10 -19
-10 -10 0 -21 -7 -25 -15 -3 -8 -14 -15 -25 -15 -11 0 -23 -4 -26 -10 -3 -5
-15 -10 -25 -10 -10 0 -20 -3 -22 -7 -1 -5 -21 -16 -43 -27 -22 -10 -48 -22
-57 -27 -10 -5 -24 -9 -32 -9 -8 0 -16 -7 -20 -15 -3 -8 -14 -15 -25 -15 -11
0 -23 -4 -26 -10 -3 -5 -14 -10 -24 -10 -10 0 -24 -7 -31 -15 -7 -8 -21 -15
-31 -15 -11 0 -19 -3 -19 -7 0 -11 -50 -33 -71 -33 -10 0 -22 -6 -26 -12 -11
-18 -49 -38 -72 -38 -10 0 -24 -7 -31 -15 -7 -8 -17 -15 -22 -15 -14 0 -163
-73 -166 -82 -2 -4 -14 -8 -27 -8 -13 0 -27 -4 -30 -10 -3 -5 -23 -17 -43 -26
-20 -8 -44 -20 -52 -25 -8 -5 -26 -14 -40 -20 -14 -6 -38 -16 -55 -24 -16 -7
-37 -16 -45 -19 -8 -3 -28 -15 -45 -25 -16 -11 -40 -20 -52 -20 -13 -1 -23 -5
-23 -10 0 -12 -57 -41 -81 -41 -9 0 -19 -3 -21 -7 -1 -5 -28 -20 -58 -36 -89
-45 -114 -36 -71 27 15 22 36 59 46 81 10 22 26 47 37 56 10 9 18 21 18 26 0
18 29 69 50 88 11 10 20 24 20 32 0 14 18 50 35 69 6 6 22 33 35 60 14 27 27
51 30 54 8 8 30 43 46 75 8 17 24 44 34 60 11 17 26 42 32 58 7 15 15 27 19
27 4 0 10 11 13 25 4 14 11 25 16 25 6 0 10 8 10 18 0 10 9 28 20 40 11 12 20
25 20 30 0 5 7 15 15 22 8 7 15 21 15 31 0 11 5 19 10 19 6 0 10 7 10 15 0 9
11 27 25 41 14 14 25 29 25 34 0 12 37 78 61 109 11 13 19 30 19 37 0 7 6 19
13 26 16 18 41 62 68 121 12 26 25 47 29 47 4 0 10 8 14 18 7 22 42 82 56 96
5 5 10 18 10 28 0 10 5 18 10 18 6 0 10 6 10 14 0 7 11 25 25 40 14 15 25 33
25 41 0 8 5 17 10 20 6 3 10 13 10 21 0 8 4 20 10 27 47 58 60 77 61 88 0 8 9
28 19 44 10 17 31 53 46 80 14 28 30 52 33 55 4 3 13 21 21 40 7 19 23 46 35
60 11 14 26 36 32 50 6 14 18 42 27 63 10 20 21 37 25 37 5 0 21 25 36 55 14
30 31 55 36 55 5 0 9 8 9 18 0 9 9 28 20 42 11 14 20 31 20 38 0 7 7 15 15 18
8 3 17 15 21 27 10 32 74 118 121 164 24 22 43 48 43 57 0 9 7 19 15 22 8 4
15 18 15 33 0 14 9 37 20 51 11 14 20 30 20 37 0 7 7 16 16 21 14 8 14 12 3
30 -11 18 -9 24 19 56 24 28 32 46 32 75 0 22 6 44 15 51 8 7 15 26 15 42 0
31 21 58 60 74 14 6 43 21 65 32 22 12 65 32 95 47 30 14 62 30 70 35 25 16
120 60 129 60 5 0 11 3 13 8 5 11 93 52 112 52 9 0 16 4 16 9 0 11 56 41 77
41 7 0 13 5 13 10 0 6 11 10 25 10 14 0 25 4 25 9 0 11 56 41 77 41 7 0 13 5
13 10 0 6 9 10 19 10 11 0 22 4 25 9 7 10 105 61 118 61 5 0 25 10 46 23 20
13 53 29 72 37 19 8 37 16 40 19 10 12 98 51 113 51 9 0 20 6 24 13 10 16 49
37 68 37 8 0 15 5 15 10 0 6 11 10 25 10 14 0 25 4 25 9 0 11 56 41 77 41 7 0
13 5 13 10 0 6 11 10 25 10 14 0 25 4 25 8 0 4 26 21 58 36 31 16 73 37 92 47
19 10 51 25 70 34 19 9 44 22 55 27 11 6 43 22 70 35 28 13 59 29 70 36 11 6
45 24 75 41 30 16 82 44 115 63 33 18 68 36 78 39 9 4 17 10 17 15 0 5 11 9
25 9 14 0 25 5 25 10 0 6 9 10 20 10 11 0 20 3 20 8 0 11 124 82 144 82 8 0
16 7 20 15 3 8 30 26 59 40 30 13 60 31 67 40 7 8 18 15 25 15 7 0 23 10 37
23 13 12 37 28 54 36 49 23 57 27 108 55 64 36 76 49 76 85 0 30 -21 43 -35
22z m-3845 -2094 c0 -19 -30 -77 -40 -77 -4 0 -10 -8 -13 -17 -4 -10 -16 -34
-29 -54 -13 -19 -38 -61 -56 -91 -18 -31 -40 -62 -47 -68 -8 -7 -15 -18 -15
-24 0 -6 -16 -35 -35 -63 -19 -29 -35 -56 -35 -60 0 -5 -11 -20 -25 -35 -14
-15 -25 -32 -25 -39 0 -6 -16 -34 -35 -60 -19 -27 -35 -54 -35 -59 0 -6 -4
-10 -8 -10 -5 0 -14 -16 -22 -35 -8 -19 -17 -35 -21 -35 -4 0 -14 -12 -22 -27
-34 -66 -46 -86 -54 -92 -5 -3 -21 -32 -36 -63 -15 -32 -31 -58 -35 -58 -5 0
-16 -19 -26 -42 -10 -24 -29 -54 -41 -68 -12 -14 -29 -41 -38 -61 -9 -19 -25
-43 -37 -54 -11 -10 -20 -24 -20 -32 0 -16 -44 -88 -60 -98 -5 -3 -10 -13 -10
-21 0 -7 -16 -36 -35 -63 -19 -26 -35 -52 -35 -55 0 -4 -13 -27 -30 -51 -16
-24 -30 -47 -30 -51 0 -4 -16 -31 -35 -60 -19 -29 -35 -57 -35 -62 0 -5 -9
-17 -20 -27 -12 -11 -28 -35 -37 -54 -21 -44 -65 -115 -75 -119 -5 -2 -8 -12
-8 -21 0 -10 -9 -26 -20 -36 -11 -10 -20 -24 -20 -30 0 -7 -7 -18 -15 -25 -8
-7 -15 -20 -15 -30 0 -10 -7 -24 -16 -31 -9 -7 -20 -22 -25 -34 -11 -22 -21
-40 -62 -102 -15 -24 -27 -46 -27 -49 0 -4 -16 -31 -35 -60 -19 -29 -35 -56
-35 -60 0 -3 -16 -29 -35 -55 -19 -27 -35 -56 -35 -64 0 -8 -4 -15 -8 -15 -5
0 -16 -16 -25 -35 -9 -19 -21 -35 -27 -35 -5 0 -10 -11 -10 -25 0 -14 -3 -25
-8 -25 -4 0 -16 -16 -26 -35 -11 -19 -23 -35 -28 -35 -4 0 -8 -7 -8 -16 0 -9
-14 -36 -30 -61 -17 -25 -42 -67 -56 -94 -13 -27 -28 -49 -32 -49 -4 0 -14
-17 -21 -37 -7 -21 -23 -49 -35 -63 -27 -32 -46 -66 -46 -83 0 -8 -9 -22 -21
-33 -11 -10 -31 -41 -44 -70 -13 -28 -29 -57 -37 -65 -8 -8 -26 -40 -42 -71
-15 -32 -31 -58 -36 -58 -4 0 -13 -14 -19 -31 -9 -27 -36 -75 -98 -174 -7 -11
-20 -35 -29 -52 -9 -18 -20 -33 -25 -33 -5 0 -9 -9 -9 -20 0 -11 -7 -23 -15
-26 -8 -4 -15 -12 -15 -20 0 -13 -45 -92 -59 -104 -12 -10 -31 -50 -31 -64 0
-8 -11 -23 -25 -34 -14 -11 -25 -27 -25 -35 0 -9 -4 -19 -10 -22 -5 -3 -10
-13 -10 -22 0 -9 -11 -30 -23 -47 -13 -17 -32 -49 -42 -71 -10 -22 -24 -47
-31 -55 -16 -20 -73 -120 -80 -142 -4 -10 -10 -18 -15 -18 -5 0 -9 -6 -9 -14
0 -7 -11 -25 -25 -40 -14 -15 -25 -36 -25 -47 0 -10 -4 -19 -10 -19 -5 0 -10
-7 -10 -17 0 -9 -11 -30 -24 -48 -34 -44 -66 -103 -66 -120 0 -8 -6 -18 -13
-22 -6 -4 -22 -28 -35 -53 -49 -95 -91 -165 -101 -168 -6 -2 -11 -9 -11 -16 0
-20 -49 -112 -70 -131 -11 -10 -20 -26 -20 -37 0 -10 -4 -18 -10 -18 -5 0 -10
-9 -10 -19 0 -11 -4 -22 -9 -26 -6 -3 -22 -30 -36 -60 -15 -30 -33 -59 -41
-66 -8 -6 -14 -18 -14 -25 0 -8 -4 -14 -10 -14 -5 0 -10 -8 -10 -17 0 -10 -10
-34 -22 -53 -43 -67 -47 -75 -53 -90 -4 -8 -10 -22 -15 -30 -5 -8 -23 -42 -39
-75 -16 -33 -39 -70 -50 -82 -12 -12 -21 -28 -22 -35 0 -7 -13 -35 -29 -63
-15 -27 -40 -74 -56 -103 -15 -30 -33 -56 -41 -59 -7 -3 -13 -13 -13 -23 0
-18 -29 -77 -96 -193 -19 -32 -34 -62 -34 -68 0 -5 -4 -9 -10 -9 -5 0 -10 -7
-10 -15 0 -9 -11 -33 -25 -55 -14 -22 -25 -46 -25 -55 0 -8 -4 -15 -10 -15 -5
0 -10 -7 -10 -15 0 -9 -16 -41 -35 -71 -19 -30 -35 -60 -35 -67 0 -8 -10 -23
-22 -34 -30 -27 -60 -16 -78 29 -8 18 -20 37 -27 41 -7 4 -13 17 -13 27 0 11
-4 20 -10 20 -5 0 -10 9 -10 19 0 11 -4 23 -10 26 -5 3 -10 14 -10 25 0 10 -6
20 -12 23 -7 2 -26 29 -41 58 -16 30 -41 77 -57 104 -15 28 -29 57 -29 66 -1
9 -8 23 -17 30 -16 13 -35 47 -89 159 -15 30 -33 62 -40 70 -20 23 -55 89 -55
104 0 8 -7 16 -15 20 -8 3 -15 15 -15 26 0 11 -4 20 -8 20 -5 0 -14 16 -22 35
-8 19 -16 35 -20 35 -5 0 -19 26 -54 100 -9 19 -34 62 -56 95 -21 33 -39 68
-39 78 -1 9 -5 17 -10 17 -5 0 -14 14 -20 32 -6 17 -17 39 -25 47 -21 24 -56
90 -56 106 0 8 -11 22 -25 31 -14 9 -25 24 -25 33 0 9 -20 43 -45 76 -51 67
-58 113 -25 155 11 14 20 31 20 38 0 7 7 15 15 18 8 4 15 17 15 30 0 13 5 24
10 24 6 0 10 7 10 15 0 14 26 62 40 75 3 3 11 16 18 30 7 14 21 39 32 55 11
17 22 37 26 45 9 25 53 111 89 175 18 33 47 87 64 120 17 33 42 78 56 100 14
22 30 50 35 63 5 12 14 22 20 22 5 0 10 11 10 25 0 14 5 25 10 25 6 0 10 6 10
14 0 16 25 66 37 75 5 3 21 32 36 64 15 31 31 57 36 57 4 0 8 6 8 13 0 18 41
105 54 112 5 4 21 30 35 58 14 29 33 66 42 82 10 17 24 46 32 65 8 19 20 40
26 45 17 14 54 83 54 100 0 8 7 15 15 15 9 0 15 9 15 25 0 14 5 25 10 25 6 0
10 6 10 14 0 17 25 67 38 75 5 3 21 31 35 61 15 30 33 66 41 80 7 14 22 42 32
63 11 21 27 45 37 54 9 9 17 23 18 32 0 9 9 30 19 46 10 17 31 55 46 85 16 30
38 69 51 86 13 17 23 36 23 42 0 7 5 12 10 12 6 0 10 6 10 14 0 15 55 122 75
146 7 8 30 51 51 95 21 44 47 89 56 99 10 11 18 26 18 33 0 8 7 16 15 19 8 4
15 12 15 20 0 8 4 22 9 32 5 9 17 34 27 55 10 20 21 37 25 37 3 0 16 21 29 48
35 73 53 107 60 112 4 3 19 29 34 58 14 28 31 52 36 52 6 0 10 9 10 20 0 11 9
29 20 40 11 11 20 23 20 27 0 14 73 158 82 161 4 2 8 11 8 21 0 22 51 71 73
71 9 0 17 4 17 10 0 5 11 12 25 16 14 3 25 10 25 15 0 5 8 9 18 10 9 0 31 9
47 19 17 11 46 26 65 35 38 18 96 47 139 69 14 7 49 25 76 38 28 14 126 64
218 112 93 47 175 86 182 86 7 0 15 4 17 9 2 5 39 26 83 48 44 22 89 45 100
51 11 6 70 35 130 65 61 30 130 65 155 77 25 13 56 28 70 34 14 6 27 14 30 17
9 10 73 39 87 39 7 0 13 5 13 10 0 6 9 10 19 10 11 0 22 4 26 9 7 13 64 41 82
41 7 0 13 5 13 10 0 6 6 10 14 10 7 0 19 7 26 15 7 8 21 15 31 15 11 0 19 5
19 10 0 6 8 10 18 11 9 0 31 9 47 20 17 10 41 24 55 29 14 6 65 31 114 56 49
24 95 44 102 44 7 0 14 4 16 8 4 10 108 62 124 62 6 0 14 7 18 15 3 8 15 15
26 15 11 0 20 5 20 10 0 6 6 10 13 10 18 0 75 28 82 41 4 5 15 9 26 9 10 0 19
5 19 10 0 6 8 10 18 11 9 0 31 9 47 19 17 11 45 26 63 35 72 34 101 49 192 96
97 50 130 59 130 36z m-3972 -1998 c3 -18 -46 -132 -59 -137 -5 -2 -9 -9 -9
-16 0 -16 -52 -120 -62 -124 -4 -2 -8 -10 -8 -17 0 -17 -50 -109 -62 -113 -4
-2 -8 -11 -9 -20 0 -9 -14 -37 -30 -62 -16 -25 -32 -53 -35 -62 -4 -10 -10
-18 -15 -18 -5 0 -9 -9 -9 -21 0 -11 -11 -37 -25 -57 -14 -20 -25 -40 -25 -45
0 -11 -33 -78 -80 -162 -22 -38 -47 -86 -57 -105 -10 -19 -21 -36 -25 -38 -5
-2 -8 -12 -8 -23 0 -11 -7 -22 -15 -25 -8 -4 -15 -12 -15 -18 0 -7 -9 -31 -20
-54 -11 -22 -20 -45 -20 -50 0 -6 -7 -12 -15 -16 -8 -3 -15 -11 -15 -19 0 -20
-50 -117 -61 -117 -5 0 -9 -5 -9 -12 0 -13 -31 -76 -60 -123 -10 -16 -19 -38
-19 -47 -1 -10 -5 -18 -11 -18 -5 0 -10 -8 -10 -19 0 -10 -7 -24 -15 -31 -8
-7 -15 -19 -15 -26 0 -8 -4 -14 -10 -14 -5 0 -10 -11 -10 -25 0 -14 -4 -25
-10 -25 -5 0 -10 -6 -10 -14 0 -8 -6 -21 -13 -28 -7 -7 -26 -40 -42 -72 -45
-89 -49 -96 -57 -96 -5 0 -8 -6 -8 -13 0 -21 -30 -77 -41 -77 -5 0 -9 -8 -9
-18 0 -10 -4 -22 -9 -28 -5 -5 -14 -26 -21 -46 -6 -21 -15 -38 -19 -38 -4 0
-13 -17 -20 -37 -8 -21 -28 -60 -45 -88 -18 -27 -40 -66 -49 -85 -10 -19 -26
-48 -37 -65 -10 -16 -19 -38 -19 -47 -1 -10 -5 -18 -11 -18 -5 0 -10 -9 -10
-20 0 -11 -7 -23 -15 -26 -8 -4 -15 -12 -15 -18 0 -12 -19 -52 -92 -186 -22
-41 -49 -93 -59 -115 -11 -22 -24 -47 -29 -55 -5 -8 -21 -40 -35 -70 -14 -30
-30 -62 -35 -70 -5 -8 -17 -31 -27 -50 -9 -19 -31 -57 -47 -85 -17 -27 -45
-77 -61 -110 -17 -33 -33 -61 -37 -63 -5 -2 -8 -10 -8 -18 0 -7 -10 -30 -21
-51 -12 -21 -51 -94 -86 -163 -36 -69 -71 -132 -79 -141 -8 -8 -14 -20 -14
-25 0 -15 -50 -113 -61 -120 -5 -3 -9 -14 -9 -25 0 -10 -4 -19 -10 -19 -5 0
-10 -6 -10 -14 0 -18 -32 -76 -42 -76 -5 0 -8 -7 -8 -14 0 -8 -10 -34 -23 -58
-25 -45 -79 -145 -96 -175 -6 -10 -19 -36 -29 -58 -26 -55 -71 -146 -83 -167
-9 -17 -29 -55 -119 -223 -23 -44 -46 -81 -51 -83 -5 -2 -9 -12 -9 -23 0 -10
-4 -19 -10 -19 -5 0 -10 -8 -11 -17 0 -10 -9 -31 -20 -48 -11 -16 -22 -38 -25
-47 -8 -22 -98 -200 -149 -293 -21 -38 -54 -100 -73 -137 -19 -38 -38 -68 -43
-68 -5 0 -9 -11 -9 -25 0 -14 -4 -25 -10 -25 -5 0 -10 -6 -10 -13 0 -21 -30
-77 -41 -77 -5 0 -9 -11 -9 -25 0 -14 -4 -25 -10 -25 -5 0 -10 -7 -10 -15 0
-19 -21 -58 -37 -68 -7 -4 -13 -14 -13 -22 0 -21 -42 -115 -52 -115 -4 0 -8
-4 -8 -10 0 -10 -44 -104 -60 -130 -5 -8 -21 -41 -35 -72 -14 -31 -30 -60 -35
-63 -6 -3 -10 -15 -10 -26 0 -10 -4 -19 -10 -19 -5 0 -10 -9 -10 -20 0 -14 -7
-20 -21 -20 -21 0 -21 1 -14 218 4 119 11 227 16 240 5 13 9 71 9 128 0 66 5
113 13 127 11 21 20 127 33 397 3 69 10 129 15 135 5 5 9 64 9 131 0 96 3 124
15 134 12 10 15 38 15 136 0 71 4 125 10 129 6 4 10 60 10 135 0 75 4 131 10
135 6 4 10 56 10 124 0 88 4 122 15 137 11 14 14 50 15 144 0 70 4 130 9 135
6 6 12 69 15 140 4 72 9 187 12 257 4 93 9 130 20 138 11 10 14 41 14 141 0
74 4 130 10 134 6 4 10 60 10 135 0 75 4 131 10 135 6 4 10 58 10 129 0 96 3
126 14 136 11 8 16 45 20 143 3 73 8 168 11 212 3 44 9 155 14 247 7 125 13
173 25 190 9 12 16 38 16 58 0 33 4 37 58 63 31 15 65 27 74 27 9 0 18 4 20 8
4 11 108 62 125 62 7 0 13 4 13 9 0 5 11 12 25 15 14 4 25 11 25 16 0 6 6 10
13 10 17 0 121 51 125 62 2 4 9 8 16 8 7 0 26 6 42 14 16 8 56 26 89 41 90 40
208 95 227 106 10 5 25 9 33 9 8 0 15 4 15 9 0 11 56 41 77 41 7 0 13 5 13 10
0 6 11 10 24 10 13 0 26 7 30 15 3 8 15 15 26 15 11 0 20 5 20 10 0 6 8 10 18
11 9 0 31 9 47 20 17 10 37 21 45 25 8 3 24 9 35 14 34 13 178 81 205 97 44
24 132 63 145 63 7 0 15 7 19 15 3 8 12 15 20 15 9 0 31 9 51 20 20 11 42 20
49 20 8 0 16 7 20 15 3 8 15 15 26 15 11 0 20 5 20 10 0 6 8 10 18 11 9 0 31
9 47 19 17 11 44 26 60 33 70 33 102 49 135 66 19 10 76 37 125 61 185 87 240
113 255 121 8 5 40 20 70 34 30 14 62 30 70 35 8 5 31 17 50 26 83 39 97 46
125 64 35 22 58 21 63 -1z m-1168 -3709 c7 -22 22 -47 32 -57 10 -9 28 -35 39
-57 11 -23 36 -68 54 -101 19 -33 39 -69 44 -80 5 -12 16 -27 25 -34 9 -7 16
-22 16 -32 0 -11 5 -19 10 -19 6 0 15 -10 20 -22 5 -13 21 -41 34 -63 14 -22
37 -62 52 -90 14 -27 33 -54 40 -58 8 -4 14 -15 14 -24 0 -8 14 -35 30 -60 26
-39 51 -81 95 -164 5 -11 13 -19 17 -19 5 0 8 -10 8 -23 0 -12 11 -31 25 -41
14 -10 25 -27 25 -37 0 -11 5 -19 10 -19 6 0 10 -8 10 -18 0 -10 8 -27 19 -38
10 -11 31 -42 46 -70 15 -27 38 -63 51 -81 13 -17 24 -37 24 -44 0 -6 15 -32
33 -58 18 -25 39 -60 46 -77 15 -39 5 -84 -19 -84 -11 0 -22 -4 -25 -10 -3 -5
-23 -17 -43 -26 -20 -9 -106 -50 -190 -90 -84 -41 -161 -74 -172 -74 -11 0
-20 -4 -20 -9 0 -11 -56 -41 -77 -41 -7 0 -13 -4 -13 -10 0 -5 -11 -10 -25
-10 -14 0 -25 -4 -25 -9 0 -11 -56 -41 -77 -41 -7 0 -13 -4 -13 -10 0 -5 -9
-10 -19 -10 -11 0 -22 -4 -25 -9 -7 -10 -105 -61 -118 -61 -5 0 -25 -10 -46
-23 -20 -13 -53 -29 -72 -37 -19 -8 -37 -17 -40 -20 -4 -6 -62 -34 -110 -54
-8 -3 -28 -15 -45 -25 -16 -11 -38 -20 -47 -20 -10 -1 -18 -5 -18 -11 0 -5
-11 -10 -25 -10 -14 0 -25 -4 -25 -9 0 -11 -56 -41 -77 -41 -7 0 -13 -4 -13
-10 0 -5 -7 -10 -15 -10 -9 0 -33 -11 -55 -25 -22 -14 -46 -25 -55 -25 -8 0
-15 -4 -15 -10 0 -5 -7 -10 -15 -10 -15 0 -55 -19 -65 -31 -3 -3 -23 -14 -45
-23 -22 -10 -49 -23 -60 -29 -54 -30 -231 -117 -238 -117 -4 0 -25 -11 -47
-25 -22 -14 -46 -25 -55 -25 -8 0 -15 -4 -15 -10 0 -5 -6 -10 -13 -10 -18 0
-75 -28 -82 -41 -4 -5 -15 -9 -26 -9 -10 0 -19 -4 -19 -10 0 -5 -6 -10 -13
-10 -18 0 -75 -28 -82 -41 -9 -14 -41 -11 -54 5 -8 10 -8 16 3 25 8 7 26 36
41 66 14 30 31 55 36 55 5 0 9 6 9 14 0 21 43 98 58 103 6 3 12 11 12 19 0 7
9 29 20 49 11 20 20 42 20 50 0 8 6 18 13 22 12 8 27 33 80 138 17 33 38 71
48 85 31 46 59 100 59 115 0 8 4 15 9 15 5 0 24 29 42 65 18 36 41 74 51 84
10 11 18 29 18 41 0 11 5 20 10 20 6 0 10 8 10 19 0 10 7 24 15 31 8 7 15 19
15 26 0 8 4 14 8 14 4 0 13 12 20 28 7 15 23 47 37 72 14 25 29 57 34 73 6 15
15 27 20 27 6 0 11 6 11 14 0 7 9 25 20 39 11 14 20 32 20 41 0 9 7 19 15 22
8 4 15 12 15 18 0 15 37 90 65 131 12 17 34 56 50 88 16 31 33 57 37 57 4 0 8
8 8 18 0 23 28 82 40 82 4 0 17 21 29 48 31 67 83 163 106 197 12 17 27 44 34
60 7 17 17 37 22 45 5 8 16 31 25 50 10 19 32 56 51 83 18 26 33 51 33 55 0
16 52 112 61 112 5 0 9 7 9 16 0 9 13 38 28 63 15 25 36 62 46 81 17 35 33 63
50 88 12 20 50 -19 66 -68z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 42 KiB

View 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

View File

@ -0,0 +1,79 @@
import { getProfile } from '@api/user'
import fuwareLogo from '@assets/logo-fuware.svg'
import { useSiteContext } from '@context/SiteContext'
import useAuth from '@hooks/useAuth'
import useToast from '@hooks/useToast'
import { A } from '@solidjs/router'
import { IconLogout, IconMenuDeep } from '@tabler/icons-solidjs'
import { Show, onMount } from 'solid-js'
export default function Header() {
const { store, setAuth, setUser } = useSiteContext()
const { clickLogOut } = useAuth(setAuth)
const notify = useToast()
onMount(async () => {
try {
const resp = await getProfile()
if (resp.status === 200) {
setUser(resp.data)
}
} catch (error) {
notify.error({
title: 'Get profile fail!',
closable: false,
description: error?.data || 'Can not get user profile!',
})
}
})
const logOut = async () => {
try {
await clickLogOut()
} catch (error) {
console.log({
status: 'danger',
title: 'Logout fail!',
closable: false,
})
}
}
return (
<header class="w-full navbar py-3 px-4 items-center justify-between bg-emerald-500">
<div class="flex items-center justify-end">
<A href="/" class="text-white flex items-center hover:text-white">
<img src={fuwareLogo} class="w-8" alt="Fuware logo" />
<span class="ml-2 text-2xl">Fuware</span>
</A>
</div>
<Show when={store.auth}>
<div class="flex items-center justify-end">
<div class="avatar hidden lg:block">
<div class="w-9 mask mask-hexagon">
<img
src={`https://ui-avatars.com/api/?name=${store.userInfo?.name}`}
alt="avatar"
/>
</div>
</div>
<A
href="/me"
class="mx-3 text-white hover:text-white hidden lg:block"
>
{store.userInfo?.name}
</A>
<button class="btn btn-ghost btn-sm hidden lg:block" onClick={logOut}>
<IconLogout size={16} />
</button>
<label
for="nav-menu"
class="btn btn-ghost btn-sm drawer-button pr-0 lg:hidden"
>
<IconMenuDeep size={25} color="white" />
</label>
</div>
</Show>
</header>
)
}

View File

@ -0,0 +1,69 @@
// import { styled } from 'solid-styled-components'
import { useSiteContext } from '@context/SiteContext'
import useAuth from '@hooks/useAuth'
import { NAV_ROUTES } from '@routes/routes'
import { A } from '@solidjs/router'
import { IconLogout, IconTriangle } from '@tabler/icons-solidjs'
import { For, Show } from 'solid-js'
import { Dynamic } from 'solid-js/web'
export default function Navbar() {
const { store, setAuth } = useSiteContext()
const { clickLogOut } = useAuth(setAuth)
const logOut = async () => {
try {
await clickLogOut()
} catch (error) {
console.log({
status: 'danger',
title: 'Logout fail!',
closable: false,
})
}
}
return (
<div class="drawer-side lg:h-[calc(100svh-64px)]">
<label for="nav-menu" aria-label="close sidebar" class="drawer-overlay" />
<div class="bg-base-200 w-80 min-h-full">
<Show when={store.auth}>
<div class="flex items-center justify-between px-5 pt-5 lg:hidden">
<div class="avatar">
<div class="w-9 mask mask-hexagon">
<img
src={`https://ui-avatars.com/api/?name=${store.userInfo?.name}`}
alt="avatar"
/>
</div>
</div>
<span class="mx-3 line-clamp-1">
<A href="/me">{store.userInfo?.name}</A>
</span>
<button class="btn btn-ghost btn-sm" onClick={logOut}>
<IconLogout size={16} />
</button>
</div>
<div class="divider divider-success mb-0 lg:hidden">
<IconTriangle size={30} />
</div>
</Show>
<ul class="menu p-4 w-80 text-base-content">
<For each={NAV_ROUTES}>
{(item) =>
item.show && (
<li class="mb-2">
<A href={item.path}>
<Dynamic component={item.icon} />
{item.text}
</A>
</li>
)
}
</For>
</ul>
</div>
</div>
)
}

View File

@ -0,0 +1,62 @@
import {
IconCircleCheck,
IconFaceIdError,
IconInfoCircle,
IconX,
} from '@tabler/icons-solidjs'
import { Show } from 'solid-js'
import { Dynamic } from 'solid-js/web'
const STATUS = Object.freeze(
new Proxy(
{
success: {
icon: IconCircleCheck,
color: 'text-green-500',
},
error: {
icon: IconFaceIdError,
color: 'text-red-500',
},
info: {
icon: IconInfoCircle,
color: 'text-blue-500',
},
},
{
get: (target, prop) =>
target[prop] ?? { icon: IconInfoCircle, color: 'text-blue-500' },
},
),
)
export default function Notify(props) {
return (
<div class="bg-white border border-slate-300 w-max h-20 shadow-lg rounded-md gap-4 p-4 flex flex-row items-center justify-center">
<section class="w-6 h-full flex flex-col items-center justify-start">
<Dynamic
component={STATUS[props.status].icon}
size={30}
class={STATUS[props.status].color}
/>
</section>
<section class="h-full flex flex-col items-start justify-end gap-1">
<Show when={props.title}>
<h1
class={`text-base font-semibold text-zinc-800 antialiased ${STATUS[props.status].color}`}
>
{props.title}
</h1>
</Show>
<p class="text-sm font-medium text-black antialiased">
{props.description}
</p>
</section>
<Show when={props.onClose}>
<section class="w-5 h-full flex flex-col items-center justify-start">
<IconX size={20} class="cursor-pointer" onclick={props.onClose} />
</section>
</Show>
</div>
)
}

View File

@ -0,0 +1,56 @@
import { 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 storeData = Helpers.decrypt(localStorage.getItem(STORE_KEY))
if (!storeData) return
setStore(storeData)
})
const setLocalStore = () => {
if (store.auth) {
localStorage.setItem(STORE_KEY, Helpers.encrypt(store))
} else {
localStorage.removeItem(STORE_KEY)
}
}
const setAuth = ({ auth, user }) => {
setStore(
produce((s) => {
s.auth = auth
s.userInfo = user
}),
)
setLocalStore()
}
const setUser = (user) => {
setStore(
produce((s) => {
s.userInfo = user
}),
)
setLocalStore()
}
return (
<SiteContext.Provider value={{ store, setAuth, setUser }}>
{props.children}
</SiteContext.Provider>
)
}
export function useSiteContext() {
return useContext(SiteContext)
}

View File

@ -0,0 +1,41 @@
import { getLogout, postLogin } from '@api/auth'
import { useNavigate } from '@solidjs/router'
import { LOGIN_KEY } from '@utils/enum'
import { Helpers } from '@utils/helper'
export default function useAuth(setAuth) {
const navigate = useNavigate()
const clickLogIn = async (username, password, cbFormReset) => {
const loginData = {
username: username,
password: password,
}
const resp = await postLogin(loginData)
if (resp.status === 200) {
const token = resp.data || {}
if (token) {
const { name, ...rest } = token
setAuth({ auth: true, user: { name } })
localStorage.setItem(LOGIN_KEY, Helpers.encrypt(JSON.stringify(rest)))
}
cbFormReset()
navigate('/', { replace: true })
}
}
const clickLogOut = async () => {
await getLogout()
Helpers.clearStorage()
setAuth({ auth: false, user: null })
navigate('/login', { replace: false })
}
return {
clickLogOut,
clickLogIn,
}
}

View 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]
}

View File

@ -0,0 +1,31 @@
import Notify from '@components/Notify'
import toast from 'solid-toast'
export default function useToast() {
const notify = {}
notify.show = ({ status, title, description, closable = false }) => {
return toast.custom((t) => (
<Notify
status={status}
title={title}
description={description}
onClose={closable ? () => toast.dismiss(t.id) : null}
/>
))
}
notify.success = ({ title, description, closable = false }) => {
return notify.show({ status: 'success', title, description, closable })
}
notify.error = ({ title, description, closable = false }) => {
return notify.show({ status: 'error', title, description, closable })
}
notify.info = ({ title, description, closable = false }) => {
return notify.show({ status: 'info', title, description, closable })
}
return notify
}

35
frontend/src/index.css Normal file
View File

@ -0,0 +1,35 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
: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
frontend/src/index.jsx Normal file
View 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,
)

View File

@ -0,0 +1,4 @@
{
"login": "Login",
"logout": "Logout"
}

15
frontend/src/lang/vi.json Normal file
View File

@ -0,0 +1,15 @@
{
"ui": {
"username": "Tên người dùng",
"password": "Mật khẩu",
"login": "Đăng Nhập",
"logout": "Đăng xuất",
"dashboard": "Bảng điều khiển",
"profile": "Hồ sơ"
},
"message": {
"CREATED_USER": "Username already registered!",
"LOGIN_WRONG": "Your username or password input is wrong!",
"USER_LOCK": "Your Account was locked"
}
}

View File

@ -0,0 +1,3 @@
export default function Dashboard() {
return <>Dashboard</>
}

View File

@ -0,0 +1,14 @@
import { NAV_ROUTES } from '@routes/routes'
import { useNavigate } from '@solidjs/router'
import { onMount } from 'solid-js'
export default function Home() {
const navigate = useNavigate()
onMount(() => {
const first = NAV_ROUTES.filter((item) => item.show)[0]?.path || '/me'
navigate(first, { replace: true })
})
return <></>
}

View File

@ -0,0 +1,31 @@
import Header from '@components/Header'
import Navbar from '@components/Navbar'
import { useSiteContext } from '@context/SiteContext'
import { useNavigate } from '@solidjs/router'
import { onMount } from 'solid-js'
export default function Layout(props) {
const { store } = useSiteContext()
const navigate = useNavigate()
onMount(() => {
if (!store.auth) {
navigate('/login', { replace: true })
}
})
return (
<div class="layer-top">
<Header />
<div id="main-page">
<div class="drawer lg:drawer-open">
<input id="nav-menu" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex flex-col">
<main class="main-content p-3">{props.children}</main>
</div>
<Navbar />
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,223 @@
import { useSiteContext } from '@context/SiteContext'
import useLanguage from '@hooks/useLanguage'
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'
import logo from '@assets/logo-fuware.svg'
import useAuth from '@hooks/useAuth'
import useToast from '@hooks/useToast'
const LoginPage = styled('div')`
width: 100%;
height: 100svh;
display: flex;
padding-left: 15px;
padding-right: 15px;
background: #fff url('/images/bg-login.jpg') no-repeat fixed center;
background-size: cover;
place-items: center;
.login-wrap {
width: 40%;
max-width: 500px;
min-width: 320px;
margin: 0 auto;
overflow: hidden;
position: relative;
&:after {
content: '';
display: block;
width: 500px;
height: 500px;
border-radius: 15px;
position: absolute;
z-index: 2;
top: -120px;
left: -285px;
background: #10b981;
transform: rotate(45deg);
}
&:before {
content: '';
display: block;
width: 500px;
height: 500px;
border-radius: 15px;
position: absolute;
z-index: 2;
top: -40px;
left: -130px;
background: #ff6600;
transform: rotate(20deg);
}
.card-body {
position: relative;
z-index: 4;
}
.logo {
position: relative;
z-index: 4;
display: block;
width: 40%;
max-width: 150px;
min-width: 100px;
margin: 0 auto;
margin-top: 15px;
}
.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 { clickLogIn } = useAuth(setAuth)
const notify = useToast()
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 { username, password } = formData()
await clickLogIn(username, password, formHandler.resetForm)
notify.success({
title: 'Login success!',
description: 'Welcome back!',
closable: true,
})
} catch (error) {
notify.error({
title: 'Login fail!',
description: error?.data
? language.message[error.data]
: 'Your username or password input is wrong!',
closable: true,
})
}
}
return (
<LoginPage>
<div class="card glass card-compact login-wrap shadow-xl">
<div class="h-44">
<picture class="logo">
<source srcSet={logo} type="image/png" media="(min-width: 600px)" />
<img src={logo} alt="logo" />
</picture>
</div>
<div class="card-body">
<h1 class="card-title">{language.ui.login}</h1>
<form autoComplete="off" onSubmit={submit}>
<Field
mode="input"
name="username"
formHandler={formHandler}
render={(field) => (
<label class="form-control w-full pb-5">
<label
class="input input-bordered flex items-center gap-2 w-full"
classList={{ 'input-error': field.helpers.error }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4 opacity-70"
>
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z" />
</svg>
<input
id="username"
type="text"
class="grow w-full"
placeholder="Username"
{...field.props}
/>
</label>
<Show when={field.helpers.error}>
<div class="label">
<span class="label-text-alt text-red-600">
{field.helpers.errorMessage}
</span>
</div>
</Show>
</label>
)}
/>
<Field
mode="input"
name="password"
formHandler={formHandler}
render={(field) => (
<label class="form-control w-full">
<label
class="input input-bordered flex items-center gap-2 w-full"
classList={{ 'input-error': field.helpers.error }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4 opacity-70"
>
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z" />
</svg>
<input
id="password"
type="password"
class="grow w-full"
placeholder="Password"
{...field.props}
/>
</label>
<Show when={field.helpers.error}>
<div class="label">
<span class="label-text-alt text-red-600">
{field.helpers.errorMessage}
</span>
</div>
</Show>
</label>
)}
/>
<div class="card-actions justify-end mt-5">
<button type="submit" class="btn btn-primary">
{language.ui.login}
</button>
</div>
</form>
</div>
</div>
</LoginPage>
)
}

View File

@ -0,0 +1,3 @@
export default function NotFound() {
return <>404</>
}

View File

@ -0,0 +1,3 @@
export default function Profile() {
return <>Profile</>
}

View File

@ -0,0 +1 @@
export * from './routes'

View File

@ -0,0 +1,42 @@
import useLanguage from '@hooks/useLanguage'
import { IconDashboard } from '@tabler/icons-solidjs'
import UserHelper from '@utils/auth'
import { lazy } from 'solid-js'
const language = useLanguage()
const userHelper = new UserHelper()
console.log(userHelper.isAdmin)
export const NAV_ROUTES = [
{
path: '/dashboard',
components: lazy(() => import('@pages/Dashboard')),
filter: {},
show: false,
icon: IconDashboard,
text: language?.ui.dashboard,
},
{
path: '/profile',
components: lazy(() => import('@pages/Profile')),
filter: {},
show: true,
icon: IconDashboard,
text: language?.ui.profile,
},
]
export const ROUTES = [
{
path: '/',
components: lazy(() => import('@pages/Home')),
filter: {},
},
...NAV_ROUTES,
{
path: '/me',
components: lazy(() => import('@pages/Profile')),
filter: {},
},
]

View File

@ -0,0 +1,14 @@
import { STORE_KEY } from './enum'
import { Helpers } from './helper'
export default class UserHelper {
#user
constructor() {
this.#user = Helpers.decrypt(localStorage.getItem(STORE_KEY))
}
get isAdmin() {
return this.#user?.userInfo?.isAdmin
}
}

View File

@ -0,0 +1,5 @@
// const PRODUCTION = import.meta.env.NODE_ENV === 'production'
export const SECRET_KEY = 'bGV0IGRvIGl0IGZvciBlbmNyeXRo'
export const STORE_KEY = 'dXNlciBsb2dpbiBpbmZv'
export const LOGIN_KEY = '7fo24CMyIc'

View File

@ -0,0 +1,50 @@
import { AES, enc } from 'crypto-js'
import { LOGIN_KEY, SECRET_KEY, STORE_KEY } from './enum'
export class Helpers {
static setCookie = (cname, cvalue, exdays) => {
const d = new Date()
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000)
let expires = `expires=${d.toUTCString()}`
document.cookie = `${cname}=${cvalue};${expires};path=/`
}
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 clearStorage = () => {
localStorage.removeItem(LOGIN_KEY)
localStorage.removeItem(STORE_KEY)
}
static checkTokenExpired = (exp) => {
const currentTime = Math.floor(new Date().getTime() / 1000)
return exp < currentTime
}
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
}
}

View File

@ -0,0 +1,10 @@
import daisyui from 'daisyui'
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{js,jsx}'],
theme: {
extend: {},
},
plugins: [daisyui],
}

73
frontend/vite.config.js Normal file
View 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(({ 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',
},
},
}
})