improve: add production stage

This commit is contained in:
h z
2024-12-09 07:01:22 +00:00
parent 0e6fd8409a
commit ba69541a7b
29 changed files with 616 additions and 92 deletions

View File

@@ -1,36 +1,31 @@
#!/bin/bash
rm -f /app/src/config.js;
if [ -z "$BACKEND_HOST" ]; then
BACKEND_HOST="http://localhost:5000"
fi
if [ -z "$FRONTEND_HOST" ]; then
FRONTEND_HOST="http://localhost:3000"
fi
if [ -z "$KC_CLIENT_ID" ]; then
KC_CLIENT_ID="labdev"
fi
if [ -z "$KC_HOST" ]; then
KC_HOST="https://login.hangman-lab.top"
fi
if [ -z "$KC_REALM" ]; then
KC_REALM="Hangman-Lab"
fi
#!/bin/sh
mkdir -p /app/src
BACKEND_HOST="${BACKEND_HOST:-http://localhost:5000}"
FRONTEND_HOST="${FRONTEND_HOST:-http://localhost:80}"
KC_CLIENT_ID="${KC_CLIENT_ID:-labdev}"
KC_HOST="${KC_HOST:-https://login.hangman-lab.top}"
KC_REALM="${KC_REALM:-Hangman-Lab}"
echo "
const config = {
BACKEND_HOST: \"${BACKEND_HOST}\",
FRONTEND_HOST: \"${FRONTEND_HOST}\",
KC_CLIENT_ID: \"${KC_CLIENT_ID}\",
OIDC_CONFIG: {
authority: \"${KC_HOST}/realms/${KC_REALM}\",
client_id: \"${KC_CLIENT_ID}\",
redirect_uri: \"${FRONTEND_HOST}/callback\",
post_logout_redirect_uri: \"${FRONTEND_HOST}\",
response_type: \"code\",
scope: \"openid profile email roles\",
},
};
export default config;
" > /app/src/config.js;
rm -f /usr/share/nginx/html/config.js
cat <<EOL > /usr/share/nginx/html/config.json
{
"BACKEND_HOST": "${BACKEND_HOST}",
"FRONTEND_HOST": "${FRONTEND_HOST}",
"KC_CLIENT_ID": "${KC_CLIENT_ID}",
"OIDC_CONFIG": {
"authority": "${KC_HOST}/realms/${KC_REALM}",
"client_id": "${KC_CLIENT_ID}",
"redirect_uri": "${FRONTEND_HOST}/callback",
"post_logout_redirect_uri": "${FRONTEND_HOST}",
"response_type": "code",
"scope": "openid profile email roles",
"popup_redirect_uri": "${FRONTEND_HOST}/popup_callback",
"silent_redirect_uri": "${FRONTEND_HOST}/silent_callback"
}
}
EOL
exec "$@"

View File

@@ -1,15 +1,27 @@
FROM node:18 as build-stage
FROM node:18-alpine AS build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
COPY BuildConfig.sh /app/BuildConfig.sh
RUN chmod +x /app/BuildConfig.sh
RUN npm run build
EXPOSE 3000
CMD ["/bin/bash", "-c", "/app/BuildConfig.sh && npm start"]
FROM nginx:stable-alpine AS production-stage
RUN apk add --no-cache bash
WORKDIR /app
COPY package*.json ./
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY --from=build-stage /app/public/icons /usr/share/nginx/html/icons
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY BuildConfig.sh /docker-entrypoint.d/10-build-config.sh
RUN chmod a+x /docker-entrypoint.d/10-build-config.sh
EXPOSE $FRONTEND_PORT
CMD ["nginx", "-g", "daemon off;"]

138
README.md Normal file
View File

@@ -0,0 +1,138 @@
# Hangman Lab Frontend
This project serves as a lightweight frontend solution for personal websites, designed to work seamlessly with Keycloak for authentication and authorization. It provides an easy-to-use interface for managing and publishing markdown files, allowing users to maintain their personal content effortlessly.
## Table of Contents
- [Features](#features)
- [Technologies Used](#technologies-used)
- [Installation](#installation)
- [Configuration](#configuration)
- [Development](#development)
- [Building for Production](#building-for-production)
- [Docker Deployment](#docker-deployment)
- [License](#license)
## Features
- **User Authentication**: Secure login and logout functionalities using **Keycloak** and **OpenID Connect (OIDC)**.
- **Markdown Management**: Create, edit, and view markdown files with live preview and syntax highlighting for a streamlined content creation experience.
- **Docker Support**: Containerized setup for easy deployment and scalability.
## Technologies Used
This project uses the following open-source libraries, all of which are licensed under the MIT License:
- [Bulma](https://bulma.io/) (MIT)
- [Katex](https://katex.org/) (MIT)
- [oidc-client-ts](https://github.com/authts/oidc-client-ts) (Apache-2.0)
- [React](https://reactjs.org/) (MIT)
- [React-DOM](https://reactjs.org/) (MIT)
- [React-Markdown](https://github.com/remarkjs/react-markdown) (MIT)
- [React-Query](https://react-query-v3.tanstack.com/) (MIT)
- [React-Router-DOM](https://reactrouter.com/) (MIT)
- [React-Syntax-Highlighter](https://github.com/react-syntax-highlighter/react-syntax-highlighter) (MIT)
- [Rehype-Katex](https://github.com/remarkjs/remark-math/tree/main/packages/rehype-katex) (MIT)
- [Rehype-Raw](https://github.com/rehypejs/rehype-raw) (MIT)
- [Remark-Math](https://github.com/remarkjs/remark-math) (MIT)
The development dependencies (e.g., Babel, Webpack, and loaders) are also licensed under the MIT License.
## Installation
### Prerequisites
- **Node.js** (v14.x or higher)
- **npm** (v6.x or higher) or **Yarn**
- **Docker** (optional, for containerized deployment)
- **Keycloak Server**: Ensure you have access to a Keycloak server for authentication.
### Steps
1. **Clone the Repository**
```bash
git clone https://git.hangman-lab.top/hzhang/HangmanLab.Frontend.git
cd HangmanLab.Frontend
```
2. **Install Dependencies**
Using npm:
```bash
npm install
```
Or using Yarn:
```bash
yarn install
```
3. **Configure environment variables**
See Configuration[#configuration]
4. **BuildConfig.sh**
```bash
chmod +x BuildConfig
./BuildConfig.sh
```
## Configuration
Set the following environment variables before running BuildConfig.sh
### Environment Variables
- `BACKEND_HOST`: URL of the backend server (default: `http://localhost:5000`)
- `FRONTEND_HOST`: URL of the frontend server (default: `http://localhost:3000`)
- `KC_CLIENT_ID`: Keycloak client ID
- `KC_HOST`: Keycloak server URL
- `KC_REALM`: Keycloak realm
## Development
### Running the Development Server
Start the development server with hot reloading:
```bash
npm start
```
Or using Yarn:
```bash
yarn start
```
The application will be accessible at `http://localhost:3000` by default.
## Docker Deployment
### Image
Pull the latest Docker image from the repository:
```bash
docker pull git.hangman-lab.top/hzhang/hangman-lab-frontend:latest
```
### Building the docker Image
```bash
docker build -t hangman-lab-frontend:latest .
```
### Running the Docker Container
Ensure your backend service (e.g., running at http://localhost:5000) is up and accessible. Then, run the Docker container:
```bash
docker run -d -p 80:80 --name hangman-lab-frontend hangman-lab-frontend:latest
```
#### Note:
- The container listens on port 80. Adjust the port mapping (-p) as needed.
- Make sure to set the appropriate environment variables either in the Dockerfile or via Docker environment variable options.
## License
[MIT][license] © [hzhang][author]
<!-- Definitions -->
[author]: https://hangman-lab.top
[license]: license

22
licence Normal file
View File

@@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) hzhang <hzhang@hangman-lab.top>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

13
nginx.conf Normal file
View File

@@ -0,0 +1,13 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /icons/ {
try_files $uri =404;
}
error_page 404 /index.html;
}

34
package-lock.json generated
View File

@@ -28,6 +28,7 @@
"@babel/preset-react": "^7.25.9",
"babel-loader": "^9.2.1",
"css-loader": "^7.1.2",
"dotenv-webpack": "^8.1.0",
"html-webpack-plugin": "^5.6.3",
"sass": "^1.81.0",
"sass-loader": "^16.0.3",
@@ -3611,6 +3612,39 @@
"tslib": "^2.0.3"
}
},
"node_modules/dotenv": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
"integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/dotenv-defaults": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz",
"integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==",
"dev": true,
"dependencies": {
"dotenv": "^8.2.0"
}
},
"node_modules/dotenv-webpack": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-8.1.0.tgz",
"integrity": "sha512-owK1JcsPkIobeqjVrk6h7jPED/W6ZpdFsMPR+5ursB7/SdgDyO+VzAU+szK8C8u3qUhtENyYnj8eyXMR5kkGag==",
"dev": true,
"dependencies": {
"dotenv-defaults": "^2.0.2"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"webpack": "^4 || ^5"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",

View File

@@ -31,6 +31,7 @@
"@babel/preset-react": "^7.25.9",
"babel-loader": "^9.2.1",
"css-loader": "^7.1.2",
"dotenv-webpack": "^8.1.0",
"html-webpack-plugin": "^5.6.3",
"sass": "^1.81.0",
"sass-loader": "^16.0.3",

BIN
public/icons/email.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
public/icons/git.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
public/icons/linkedin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -7,10 +7,11 @@
.content-container {
display: flex;
flex: 1;
overflow: hidden;
}
.main-content {
flex: 1;
padding: 1rem;
padding: 1rem 1rem 100px 1rem;
overflow-y: auto;
}

View File

@@ -5,11 +5,12 @@ import SideNavigation from "./components/Navigations/SideNavigation";
import MarkdownContent from "./components/Markdowns/MarkdownContent";
import MarkdownEditor from "./components/Markdowns/MarkdownEditor";
import "./App.css";
import Callback from "./Callback";
import config from "./config";
import Callback from "./components/KeycloakCallbacks/Callback";
import Footer from "./components/Footer";
import PopupCallback from "./components/KeycloakCallbacks/PopupCallback";
import SilentCallback from "./components/KeycloakCallbacks/SilentCallback";
const App = () => {
console.log(config)
return (
<Router>
<div className="app-container">
@@ -22,12 +23,15 @@ const App = () => {
<Route path="/markdown/:id" element={<MarkdownContent />} />
<Route path="/callback" element={<Callback />} />
<Route path="/test" element={<h1>TEST</h1>}></Route>
<Route path="/markdown/create" element={<MarkdownEditor />}></Route>
<Route path="/markdown/edit/:id" element={<MarkdownEditor />}></Route>
<Route path="/markdown/create" element={<MarkdownEditor />} />
<Route path="/markdown/edit/:id" element={<MarkdownEditor />} />
<Route path="/popup_callback" element={<PopupCallback />} />
<Route path="silent_callback" element={<SilentCallback />} />
</Routes>
</main>
</div>
</div>
<Footer />
</Router>
);
};

View File

@@ -1,6 +1,7 @@
import React, { createContext, useEffect, useMemo, useState } from "react";
// src/AuthProvider.js
import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
import { UserManager } from "oidc-client-ts";
import config from "./config";
import { ConfigContext } from "./ConfigProvider";
export const AuthContext = createContext({
user: null,
@@ -10,11 +11,20 @@ export const AuthContext = createContext({
});
const AuthProvider = ({ children }) => {
const { config, isLoading, error } = useContext(ConfigContext);
const [user, setUser] = useState(null);
const [roles, setRoles] = useState([]);
const userManager = useMemo(() => new UserManager(config.OIDC_CONFIG), []);
const userManager = useMemo(() => {
if (config && config.OIDC_CONFIG) {
return new UserManager(config.OIDC_CONFIG);
}
return null;
}, [config]);
useEffect(() => {
if (isLoading || error || !userManager) return;
userManager.getUser()
.then((user) => {
if (user && !user.expired) {
@@ -62,19 +72,23 @@ const AuthProvider = ({ children }) => {
userManager.events.removeUserLoaded(onUserLoaded);
userManager.events.removeUserUnloaded(onUserUnloaded);
};
}, [userManager]);
}, [userManager, isLoading, error, config]);
const login = () => {
userManager
.signinRedirect()
.catch((err) => {
console.log(config);
console.log(err);
});
if (userManager) {
userManager
.signinRedirect()
.catch((err) => {
console.log(config);
console.log(err);
});
}
};
const logout = () => {
userManager.signoutRedirect();
if (userManager) {
userManager.signoutRedirect();
}
};
return (

62
src/ConfigProvider.js Normal file
View File

@@ -0,0 +1,62 @@
import React, { createContext, useContext, useEffect, useState } from "react";
export const ConfigContext = createContext({
config: {
BACKEND_HOST: null,
FRONTEND_HOST: null,
KC_CLIENT_ID: null,
OIDC_CONFIG: {},
},
isLoading: true,
error: null,
});
export const useConfig = () => useContext(ConfigContext).config;
const ConfigProvider = ({ children }) => {
const [config, setConfig] = useState({
BACKEND_HOST: null,
FRONTEND_HOST: null,
KC_CLIENT_ID: null,
OIDC_CONFIG: {},
});
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/config.json')
.then((res) => {
if (!res.ok) {
throw new Error(`Failed to fetch config: ${res.statusText}`);
}
return res.json();
})
.then((data) => {
console.log(data);
setConfig(data);
setIsLoading(false);
})
.catch((err) => {
console.error("Error fetching config:", err);
setError(err);
setIsLoading(false);
});
}, []);
if (isLoading) {
return <div>Loading configuration...</div>;
}
if (error) {
return <div>Error loading configuration: {error.message}</div>;
}
return (
<ConfigContext.Provider value={{ config, isLoading, error }}>
{children}
</ConfigContext.Provider>
);
}
export default ConfigProvider;

97
src/components/Footer.css Normal file
View File

@@ -0,0 +1,97 @@
.footer {
background-color: #333;
color: #fff;
padding: 1rem;
position: fixed;
width: 100%;
display: flex;
bottom: 0;
flex-direction: column;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
overflow: visible;
max-height: 5rem;
transition: max-height 0.3s ease;
}
.footer.expanded {
max-height: 20rem;
}
.footer-details{
opacity: 0;
max-height: 0;
overflow: hidden;
transition: opacity 0.3s ease, max-height 0.3s ease;
}
.footer-details.expanded {
opacity: 1;
max-height: 15rem;
}
.footer-content {
margin-bottom: auto;
text-align: center;
}
.footer-icons {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: flex-end;
margin-right: 1rem;
}
.footer-icon {
width: 40px;
height: 40px;
transition: transform 0.2s;
}
.footer-icon:hover {
transform: scale(1.2);
}
.footer-icons a {
display: flex;
align-items: center;
gap: 0.5rem;
color: #00d1b2;
text-decoration: none;
}
.footer-icons a:hover {
color: #00a6a2;
}
.toggle-button {
width: 2rem;
height: 2rem;
border-radius: 50%;
background-color: #007bff;
color: white;
border: none;
font-size: 1.2rem;
font-weight: bold;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
transition: transform 0.2s ease, background-color 0.3s ease;
position: absolute;
top: -1rem;
left: 50%;
transform: translateX(-50%);
}
.toggle-button:hover {
background-color: #0056b3;
transform: scale(1.1) translateX(-50%);
}
.toggle-button:focus {
outline: none;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);
}

78
src/components/Footer.js Normal file
View File

@@ -0,0 +1,78 @@
import React from "react";
import "./Footer.css";
const Footer = () => {
const [isExpanded, setIsExpanded] = React.useState(false);
const [isVisible, setIsVisible] = React.useState(false);
const toggleExpand = () => {
if(!isExpanded) {
setIsVisible(true);
}
setIsExpanded((prev) => !prev);
};
const onTransitionEnd = () => {
if(!isExpanded) {
setIsVisible(false);
}
};
return (
<footer className={`footer ${isExpanded ? "expanded" : ""}`}>
<button
className="toggle-button"
onClick={toggleExpand}
type="button"
>
{isExpanded ? "↓" : "↑"}
</button>
<div className={`footer-content`}>
<p>&copy; {new Date().getFullYear()} Hangman Lab. All rights reserved.</p>
{
isVisible && (
<div
className={`footer-details ${isExpanded ? "expanded" : ""}`}
onTransitionEnd={onTransitionEnd}
>
<div className="footer-icons">
<a
href="https://www.linkedin.com/in/zhhrozhh/"
target="_blank"
rel="noopener noreferrer"
>
<img
src="/icons/linkedin.png"
alt="LinkedIn"
className="footer-icon"
/>
LinkedIn
</a>
<a
href="https://git.hangman-lab.top/hzhang/HangmanLab"
target="_blank"
rel="noopener noreferrer"
>
<img
src="/icons/git.png"
alt="GitHub"
className="footer-icon"
/>
GitHub
</a>
<a href="mailto:hzhang@hangman-lab.top">
<img
src="/icons/email.png"
alt="Email"
className="footer-icon"
/>
Email
</a>
</div>
</div>
)
}
</div>
</footer>
);
};
export default Footer;

View File

@@ -1,9 +1,10 @@
import React, { useEffect } from "react";
import React, {useContext, useEffect} from "react";
import { UserManager } from "oidc-client-ts";
import config from "./config";
import {ConfigContext} from "../../ConfigProvider";
const Callback = () => {
const config = useContext(ConfigContext).config;
useEffect(() => {
const userManager = new UserManager(config.OIDC_CONFIG);
userManager.signinRedirectCallback()

View File

@@ -0,0 +1,24 @@
import React, { useEffect, useContext } from "react";
import { UserManager } from "oidc-client-ts";
import {ConfigContext} from "../../ConfigProvider";
const PopupCallback = () => {
const { config } = useContext(ConfigContext);
useEffect(() => {
const userManager = new UserManager(config.OIDC_CONFIG);
userManager.signinPopupCallback()
.then(() => {
window.close();
})
.catch((err) => {
console.error("Popup callback error:", err);
window.close();
});
}, [config]);
return <div>Processing...</div>;
};
export default PopupCallback;

View File

@@ -0,0 +1,21 @@
import React, { useEffect, useContext } from "react";
import { UserManager } from "oidc-client-ts";
import { ConfigContext } from "../../ConfigProvider";
const SilentCallback = () => {
const { config } = useContext(ConfigContext);
useEffect(() => {
const userManager = new UserManager(config.OIDC_CONFIG);
userManager.signinSilentCallback()
.then(() => {
})
.catch((err) => {
console.error("Silent callback error:", err);
});
}, [config]);
return <div>Renew...</div>;
};
export default SilentCallback;

View File

@@ -80,7 +80,6 @@
font-size: 1.2rem;
}
code {
font-family: 'Courier New', Courier, monospace;
background-color: #f4f4f4;

View File

@@ -32,18 +32,18 @@ pre {
.markdown-preview ul,
.markdown-preview ol {
padding-left: 1.5rem; /* 设置左侧缩进 */
margin-bottom: 1rem; /* 每个列表的底部间距 */
padding-left: 1.5rem;
margin-bottom: 1rem;
}
.markdown-preview ul {
list-style-type: disc; /* 确保无序列表使用圆点 */
list-style-type: disc;
}
.markdown-preview ol {
list-style-type: decimal; /* 确保有序列表使用数字 */
list-style-type: decimal;
}
.markdown-preview li {
margin-bottom: 0.5rem; /* 列表项之间的间距 */
margin-bottom: 0.5rem;
}

View File

@@ -1,9 +1,10 @@
/* src/components/SideNavigation.css */
.menu {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
background-color: #f9f9f9;
height: 100%;
overflow-y: auto;
}
.menu-label {

View File

@@ -2,10 +2,10 @@ import PermissionGuard from "../PermissionGuard";
import PathNode from "./PathNode";
import "./SideNavigation.css";
import {useDeletePath, usePaths, useUpdatePath} from "../../utils/path-queries";
import React from 'react';
const SideNavigation = () => {
const {data: paths, isLoading, error} = usePaths(1);
const {data: paths, isLoading, error } = usePaths(1);
const deletePath = useDeletePath();
const updatePath = useUpdatePath();
@@ -20,7 +20,7 @@ const SideNavigation = () => {
};
const handleSave = (id, newName) => {
updatePath.mutate({ id, data: {name: newName}} , {
updatePath.mutate({ id, data: {name: newName }} , {
onError: (err) => {
alert("Failed to update path");
}

View File

@@ -1,11 +1,10 @@
// src/components/PathManager.js
import React, { useEffect, useState, useRef } from "react";
import React, {useEffect, useState, useRef, useContext} from "react";
import { useCreatePath, usePaths } from "../utils/path-queries";
import { useQueryClient } from "react-query";
import "./PathManager.css";
import {fetch_} from "../utils/request-utils";
import config from "../config";
import {ConfigContext} from "../ConfigProvider";
const PathManager = ({ currentPathId = 1, onPathChange }) => {
const [currentPath, setCurrentPath] = useState([{ name: "Root", id: 1 }]);
@@ -17,6 +16,7 @@ const PathManager = ({ currentPathId = 1, onPathChange }) => {
const queryClient = useQueryClient();
const { data: subPaths, isLoading: isSubPathsLoading, error: subPathsError } = usePaths(currentPathId);
const createPath = useCreatePath();
const config = useContext(ConfigContext).config;
const buildPath = async (pathId) => {
const path = [];

View File

@@ -1,8 +0,0 @@
const config = {
BACKEND_HOST: null,
FRONTEND_HOST: null,
KC_CLIENT_ID: null,
OIDC_CONFIG: {},
}
export default config;

View File

@@ -4,6 +4,7 @@ import App from "./App";
import AuthProvider, {AuthContext} from "./AuthProvider";
import "bulma/css/bulma.min.css";
import {QueryClient, QueryClientProvider} from "react-query"
import ConfigProvider from "./ConfigProvider";
const queryClient = new QueryClient({
defaultOptions: {
@@ -46,11 +47,13 @@ const EnhancedAuthProvider = ({children}) => {
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<QueryClientProvider client={queryClient}>
<AuthProvider>
<EnhancedAuthProvider>
<App />
</EnhancedAuthProvider>
</AuthProvider>
</QueryClientProvider>
<ConfigProvider>
<QueryClientProvider client={queryClient}>
<AuthProvider>
<EnhancedAuthProvider>
<App />
</EnhancedAuthProvider>
</AuthProvider>
</QueryClientProvider>
</ConfigProvider>
);

View File

@@ -1,8 +1,11 @@
import {useQuery, useMutation, useQueryClient} from 'react-query';
import {fetch_} from "./request-utils";
import config from "../config";
import {useConfig} from "../ConfigProvider";
export const useMarkdown = (id) => {
const config = useConfig();
return useQuery(
["markdown", id],
() => fetch_(`${config.BACKEND_HOST}/api/markdown/${id}`),
@@ -13,6 +16,7 @@ export const useMarkdown = (id) => {
export const useIndexMarkdown = (path_id) => {
const queryClient = useQueryClient();
const config = useConfig();
return useQuery(
["index_markdown", path_id],
() => fetch_(`${config.BACKEND_HOST}/api/markdown/get_index/${path_id}`),{
@@ -26,6 +30,7 @@ export const useIndexMarkdown = (path_id) => {
};
export const useMarkdownsByPath = (pathId) => {
const config = useConfig();
return useQuery(
["markdownsByPath", pathId],
() => fetch_(`${config.BACKEND_HOST}/api/markdown/by_path/${pathId}`),
@@ -36,6 +41,7 @@ export const useMarkdownsByPath = (pathId) => {
export const useSaveMarkdown = () => {
const queryClient = useQueryClient();
const config = useConfig();
return useMutation(({id, data}) => {
const url = id
? `${config.BACKEND_HOST}/api/markdown/${id}`

View File

@@ -1,10 +1,11 @@
import { useQuery, useMutation, useQueryClient } from "react-query";
import { fetch_ } from "./request-utils";
import config from "../config";
import {useConfig} from "../ConfigProvider";
export const usePaths = (parent_id) => {
const queryClient = useQueryClient();
const config = useConfig();
return useQuery(
["paths", parent_id],
() => fetch_(`${config.BACKEND_HOST}/api/path/parent/${parent_id}`),
@@ -24,6 +25,7 @@ export const usePaths = (parent_id) => {
export const usePath = (id) => {
const config = useConfig();
const queryClient = useQueryClient();
const cachedData = queryClient.getQueryData(["path", id]);
@@ -41,6 +43,7 @@ export const usePath = (id) => {
};
export const useCreatePath = () => {
const config = useConfig();
const queryClient = useQueryClient();
return useMutation(
@@ -58,6 +61,7 @@ export const useCreatePath = () => {
};
export const useUpdatePath = () => {
const config = useConfig();
const queryClient = useQueryClient();
return useMutation(
@@ -75,6 +79,7 @@ export const useUpdatePath = () => {
};
export const useDeletePath = () => {
const config = useConfig();
const queryClient = useQueryClient();
return useMutation(

View File

@@ -1,13 +1,13 @@
//webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const Dotenv = require('dotenv-webpack');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'bundle.js',
publicPath: '/',
publicPath: './',
clean: true,
},
module: {
@@ -28,6 +28,7 @@ module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html",
inject: true
}),
],
devServer: {