diff --git a/.gitignore b/.gitignore index 872032e..0b2c823 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,10 @@ dist-ssr *.local secret.env +privkey.pem +fullchain.pem +gin.log + # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/.vscode/settings.json b/.vscode/settings.json index ce758c7..fd2040b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,7 @@ { "cSpell.words": [ - "godotenv" + "downvotes", + "godotenv", + "upvotes" ] } \ No newline at end of file diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..d952794 --- /dev/null +++ b/compose.yml @@ -0,0 +1,25 @@ +services: + + nginx: + build: ./enshi + ports: + - 127.0.0.1:80:80 + - 127.0.0.1:443:443 + networks: + - app-network + restart: unless-stopped + + enshi_back: + build: ./enshi_back + ports: + - 127.0.0.1:9876:9876 + networks: + - app-network + environment: + - ENV=docker + - DOMAIN=localhost + restart: unless-stopped + +networks: + app-network: + driver: bridge \ No newline at end of file diff --git a/enshi/.dockerignore b/enshi/.dockerignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/enshi/.dockerignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/enshi/Dockerfile b/enshi/Dockerfile new file mode 100644 index 0000000..119c7a6 --- /dev/null +++ b/enshi/Dockerfile @@ -0,0 +1,22 @@ +FROM node:18-alpine as builder + +WORKDIR /app + +ENV VITE_ENV=docker +COPY package*.json ./ +RUN npm ci +COPY . . +RUN npm run build + + +FROM nginx:alpine + +COPY --from=builder /app/dist /usr/share/nginx/html +COPY ./nginx/nginx.conf /etc/nginx/conf.d/default.conf + +# Certificates +COPY ./nginx/fullchain.pem /etc/nginx/ssl/ +COPY ./nginx/privkey.pem /etc/nginx/ssl/ + +EXPOSE 80 +EXPOSE 443 \ No newline at end of file diff --git a/enshi/README.md b/enshi/README.md index 74872fd..7e59600 100644 --- a/enshi/README.md +++ b/enshi/README.md @@ -1,50 +1 @@ -# React + TypeScript + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: - -- Configure the top-level `parserOptions` property like this: - -```js -export default tseslint.config({ - languageOptions: { - // other options... - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - }, -}) -``` - -- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` -- Optionally add `...tseslint.configs.stylisticTypeChecked` -- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: - -```js -// eslint.config.js -import react from 'eslint-plugin-react' - -export default tseslint.config({ - // Set the react version - settings: { react: { version: '18.3' } }, - plugins: { - // Add the react plugin - react, - }, - rules: { - // other rules... - // Enable its recommended rules - ...react.configs.recommended.rules, - ...react.configs['jsx-runtime'].rules, - }, -}) -``` +# README diff --git a/enshi/eslint.config.js b/enshi/eslint.config.js deleted file mode 100644 index 092408a..0000000 --- a/enshi/eslint.config.js +++ /dev/null @@ -1,28 +0,0 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' - -export default tseslint.config( - { ignores: ['dist'] }, - { - extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - }, - plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, - }, - rules: { - ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, - }, -) diff --git a/enshi/nginx/nginx.conf b/enshi/nginx/nginx.conf new file mode 100644 index 0000000..a7b2538 --- /dev/null +++ b/enshi/nginx/nginx.conf @@ -0,0 +1,37 @@ +server { + listen 80; + server_name localhost; + + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl http2; + # server_name nekiiinkognito.ru www.nekiiinkognito.ru; + server_name localhost; + + ssl_certificate /etc/nginx/ssl/fullchain.pem; + ssl_certificate_key /etc/nginx/ssl/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers EECDH+AESGCM:EDH+AESGCM; + ssl_ecdh_curve secp384r1; + ssl_session_timeout 10m; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + + + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + } + + location /api/v1/ { + proxy_pass http://enshi_back:9876/; + } + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; +} \ No newline at end of file diff --git a/enshi/package-lock.json b/enshi/package-lock.json index d490701..c812543 100644 --- a/enshi/package-lock.json +++ b/enshi/package-lock.json @@ -8,17 +8,25 @@ "name": "enshi", "version": "0.0.0", "dependencies": { - "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-form": "^0.1.0", + "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-navigation-menu": "^1.2.1", + "@radix-ui/react-scroll-area": "^1.2.1", + "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/themes": "^3.1.3", "@tanstack/react-query": "^5.55.0", + "@tanstack/react-query-devtools": "^5.61.0", + "axios": "^1.7.7", "html-react-parser": "^5.1.16", "i18n": "^0.15.1", "i18next": "^23.14.0", "i18next-browser-languagedetector": "^8.0.0", + "immer": "^10.1.1", + "interweave": "^13.1.0", "jotai": "^2.9.3", + "jotai-immer": "^0.4.1", "primereact": "^10.8.2", "quill": "^2.0.2", "react": "^18.3.1", @@ -1226,6 +1234,42 @@ } } }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-dialog": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz", + "integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", @@ -1413,25 +1457,25 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz", - "integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz", + "integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.0", "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-dismissable-layer": "1.1.0", - "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.0", "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-portal": "1.1.1", - "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-slot": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.7" + "react-remove-scroll": "2.6.0" }, "peerDependencies": { "@types/react": "*", @@ -1448,6 +1492,158 @@ } } }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", + "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll-bar": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", @@ -1618,6 +1814,15 @@ } } }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "license": "MIT", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/@radix-ui/react-id": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", @@ -2029,17 +2234,17 @@ } }, "node_modules/@radix-ui/react-scroll-area": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.1.0.tgz", - "integrity": "sha512-9ArIZ9HWhsrfqS765h+GZuLoxaRHD/j0ZWOWilsCvYTpYJp8XwCqNG7Dt9Nu/TItKOdgLGkOPCodQvDc+UMwYg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.1.tgz", + "integrity": "sha512-FnM1fHfCtEZ1JkyfH/1oMiTcFBQvHKl4vD9WnpwkLgtF+UmnXMCad6ECPTaAjcDjam+ndOEJWgHyKDGNteWSHw==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.0", "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-presence": "1.1.1", "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0" @@ -2059,6 +2264,45 @@ } } }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-select": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.1.tgz", @@ -2212,6 +2456,130 @@ } } }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.2.tgz", + "integrity": "sha512-Z6pqSzmAP/bFJoqMAston4eSNa+ud44NSZTiZUmUen+IOZ5nBY8kzuU5WDBVyFXPtcW6yUalOHsxM/BP6Sv8ww==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-toggle": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.0.tgz", @@ -2503,6 +2871,42 @@ } } }, + "node_modules/@radix-ui/themes/node_modules/@radix-ui/react-dialog": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz", + "integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/themes/node_modules/@radix-ui/react-navigation-menu": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.0.tgz", @@ -2539,6 +2943,37 @@ } } }, + "node_modules/@radix-ui/themes/node_modules/@radix-ui/react-scroll-area": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.1.0.tgz", + "integrity": "sha512-9ArIZ9HWhsrfqS765h+GZuLoxaRHD/j0ZWOWilsCvYTpYJp8XwCqNG7Dt9Nu/TItKOdgLGkOPCodQvDc+UMwYg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/themes/node_modules/@radix-ui/react-tooltip": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.1.tgz", @@ -2807,9 +3242,19 @@ ] }, "node_modules/@tanstack/query-core": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.54.1.tgz", - "integrity": "sha512-hKS+WRpT5zBFip21pB6Jx1C0hranWQrbv5EJ7qPoiV5MYI3C8rTCqWC9DdBseiPT1JgQWh8Y55YthuYZNiw3Xw==", + "version": "5.60.6", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.60.6.tgz", + "integrity": "sha512-tI+k0KyCo1EBJ54vxK1kY24LWj673ujTydCZmzEZKAew4NqZzTaVQJEuaG1qKj2M03kUHN46rchLRd+TxVq/zQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.59.20", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.59.20.tgz", + "integrity": "sha512-vxhuQ+8VV4YWQSFxQLsuM+dnEKRY7VeRzpNabFXdhEwsBYLrjXlF1pM38A8WyKNLqZy8JjyRO8oP4Wd/oKHwuQ==", "license": "MIT", "funding": { "type": "github", @@ -2817,12 +3262,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.55.0.tgz", - "integrity": "sha512-2uYuxEbRQD8TORUiTUacEOwt1e8aoSqUOJFGY5TUrh6rQ3U85zrMS2wvbNhBhXGh6Vj69QDCP2yv8tIY7joo6Q==", + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.61.0.tgz", + "integrity": "sha512-SBzV27XAeCRBOQ8QcC94w2H1Md0+LI0gTWwc3qRJoaGuewKn5FNW4LSqwPFJZVEItfhMfGT7RpZuSFXjTi12pQ==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.54.1" + "@tanstack/query-core": "5.60.6" }, "funding": { "type": "github", @@ -2832,6 +3277,23 @@ "react": "^18 || ^19" } }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.61.0.tgz", + "integrity": "sha512-hd3yXl+KV+OGQmAw946qHAFp6DygcXcYN+1ai9idYddx6uEQyCwYk3jyIBOQEUw9uzN5DOGJLBsgd/QcimDQsA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-devtools": "5.59.20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.61.0", + "react": "^18 || ^19" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3336,6 +3798,12 @@ "node": ">=10" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -3374,6 +3842,17 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3602,6 +4081,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -3738,6 +4229,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -3926,6 +4426,12 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -4357,6 +4863,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -4374,6 +4900,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -4745,6 +5285,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -4778,6 +5328,22 @@ "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==", "license": "MIT" }, + "node_modules/interweave": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/interweave/-/interweave-13.1.0.tgz", + "integrity": "sha512-JIDq0+2NYg0cgL7AB26fBcV0yZdiJvPDBp+aF6k8gq6Cr1kH5Gd2/Xqn7j8z+TGb8jCWZn739jzalCz+nPYwcA==", + "license": "MIT", + "dependencies": { + "escape-html": "^1.0.3" + }, + "funding": { + "type": "ko-fi", + "url": "https://ko-fi.com/milesjohnson" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -4970,6 +5536,16 @@ } } }, + "node_modules/jotai-immer": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/jotai-immer/-/jotai-immer-0.4.1.tgz", + "integrity": "sha512-nQTt1HBKie/5OJDck1qLpV1PeBA6bjJLAczEYAx70PD8R4Mbu7gtexfBUCzJh6W6ecsOfwHksAYAesVth6SN9A==", + "license": "MIT", + "peerDependencies": { + "immer": ">=9.0.0", + "jotai": ">=2.0.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5185,6 +5761,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -5712,6 +6309,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/enshi/package.json b/enshi/package.json index d3a5b58..068855b 100644 --- a/enshi/package.json +++ b/enshi/package.json @@ -1,26 +1,35 @@ { "name": "enshi", "private": true, - "version": "0.0.0", + "version": "0.1.6", "type": "module", "scripts": { "dev": "vite", - "build": "tsc -b && vite build", + "lint-build": "tsc -b && vite build", + "build": "vite build", "lint": "eslint .", "preview": "vite preview" }, "dependencies": { - "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-form": "^0.1.0", + "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-navigation-menu": "^1.2.1", + "@radix-ui/react-scroll-area": "^1.2.1", + "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/themes": "^3.1.3", "@tanstack/react-query": "^5.55.0", + "@tanstack/react-query-devtools": "^5.61.0", + "axios": "^1.7.7", "html-react-parser": "^5.1.16", "i18n": "^0.15.1", "i18next": "^23.14.0", "i18next-browser-languagedetector": "^8.0.0", + "immer": "^10.1.1", + "interweave": "^13.1.0", "jotai": "^2.9.3", + "jotai-immer": "^0.4.1", "primereact": "^10.8.2", "quill": "^2.0.2", "react": "^18.3.1", diff --git a/enshi/src/@types/PostTypes.ts b/enshi/src/@types/PostTypes.ts new file mode 100644 index 0000000..9068e49 --- /dev/null +++ b/enshi/src/@types/PostTypes.ts @@ -0,0 +1,8 @@ +export type GetRandomPostsRow = { + post_id: string; + // blog_id: number; + user_id: string; + title: string; + // created_at: Date; +} + diff --git a/enshi/src/@types/UserType.ts b/enshi/src/@types/UserType.ts new file mode 100644 index 0000000..0e20c4d --- /dev/null +++ b/enshi/src/@types/UserType.ts @@ -0,0 +1,5 @@ +export type TUser = { + username: string; + isAdmin: boolean; + id?: string | number; +} \ No newline at end of file diff --git a/enshi/src/@types/index.d.ts b/enshi/src/@types/index.d.ts new file mode 100644 index 0000000..6b484a7 --- /dev/null +++ b/enshi/src/@types/index.d.ts @@ -0,0 +1,11 @@ +type TToast = { + title: string; + description?: string; + action?: React.Component; +}; + +type TExistingToast = TToast & { + id: number; + resetFunc: (arg0: boolean) => void; + open: boolean; +}; diff --git a/enshi/src/App.css b/enshi/src/App.css index 5ca1f02..73de149 100644 --- a/enshi/src/App.css +++ b/enshi/src/App.css @@ -19,6 +19,10 @@ text-transform: uppercase; } + * { + font-family: "Times New Roman"; + } + /*! * Quill Editor v1.3.6 * https://quilljs.com/ diff --git a/enshi/src/App.tsx b/enshi/src/App.tsx index 952c44e..29bf349 100644 --- a/enshi/src/App.tsx +++ b/enshi/src/App.tsx @@ -1,42 +1,26 @@ -import "./App.css"; -import "@radix-ui/themes/styles.css"; import { Theme, ThemePanel } from "@radix-ui/themes"; - -import { createBrowserRouter, RouterProvider } from "react-router-dom"; +import "@radix-ui/themes/styles.css"; import { QueryClientProvider } from "@tanstack/react-query"; -import queryClient from "./api/QueryClient/QueryClient"; -import { routes } from "./routes/routes"; -import { useEffect } from "react"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import "axios"; -import { axiosLocalhost } from "./api/axios/axios"; +import { createBrowserRouter, RouterProvider } from "react-router-dom"; +import queryClient from "./api/QueryClient/QueryClient"; +import "./App.css"; +import ToastProvider from "./Components/ToastProvider/ToastProvider"; +import { routes } from "./routes/routes"; const router = createBrowserRouter(routes); export default function App() { - useEffect(() => { - let f = async () => { - let c = await axiosLocalhost.post( - "/login", - { - nickname: "StasikChess", - password: "123456", - } - ); - - console.log(c.headers); - console.log(document.cookie); - }; - - f(); - - }, []); - return ( - - - - + + + + + + + ); } diff --git a/enshi/src/AtomStore/AtomStore.ts b/enshi/src/AtomStore/AtomStore.ts new file mode 100644 index 0000000..ed53496 --- /dev/null +++ b/enshi/src/AtomStore/AtomStore.ts @@ -0,0 +1,50 @@ +import { atom } from "jotai"; +import { atomWithStorage } from "jotai/utils"; +import { TUser } from "../@types/UserType"; + +export const userAtom = atom(); + +export const postCreationAtom = atom(); +export const postCreationTitleAtom = atom(); + +type TPostData = { + title: string; + content: string; +}; + +export const storagePostAtom = atomWithStorage( + "draft-post", + { title: "", content: "" }, + { + getItem: (key) => sessionStorage.getItem(key) as any, + setItem: (key, value) => sessionStorage.setItem(key, value as any), + removeItem: (key) => sessionStorage.removeItem(key), + }, + { getOnInit: true } +); + +export const toastAtom = atom([]); +export const setToastAtom = atom(null, (get, set, value: TToast) => { + let maxToastId = Math.max(...get(toastAtom).map((toast) => toast.id)); + maxToastId = maxToastId >= 0 ? maxToastId : 1; + let atomValueWithNewToast = get(toastAtom); + atomValueWithNewToast = [ + ...atomValueWithNewToast, + { + id: maxToastId + 1, + resetFunc: (_) => { + let currentToasts = get(toastAtom); + let afterRemoval = currentToasts.filter( + (toast) => toast.id != maxToastId + 1 + ); + set(toastAtom, afterRemoval); + }, + title: value.title, + action: value.action, + description: value.description, + open: true, + }, + ]; + + set(toastAtom, atomValueWithNewToast); +}); diff --git a/enshi/src/Components/ArticleViewer/ArcticleViewer.tsx b/enshi/src/Components/ArticleViewer/ArcticleViewer.tsx deleted file mode 100644 index 753f69d..0000000 --- a/enshi/src/Components/ArticleViewer/ArcticleViewer.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Container } from "@radix-ui/themes"; -import React from "react"; - -export default function ArcticleViewer() { - return ( - <> -
- - -
- - ); -} diff --git a/enshi/src/Components/ArticleViewer/ArticleViewer.tsx b/enshi/src/Components/ArticleViewer/ArticleViewer.tsx new file mode 100644 index 0000000..9cc5024 --- /dev/null +++ b/enshi/src/Components/ArticleViewer/ArticleViewer.tsx @@ -0,0 +1,146 @@ +import * as Dialog from "@radix-ui/react-dialog"; +import { Cross2Icon } from "@radix-ui/react-icons"; +import { + Box, + Button, + Container, + Flex, + Select, + Separator, + Text, +} from "@radix-ui/themes"; +import { useQuery } from "@tanstack/react-query"; +import { Interweave } from "interweave"; +import { useAtomValue } from "jotai"; +import { useParams } from "react-router-dom"; +import { axiosLocalhost } from "../../api/axios/axios"; +import { userAtom } from "../../AtomStore/AtomStore"; +import ChangePostButton from "./ChangePostButton/ChangePostButton"; +import SkeletonPostLoader from "./SkeletonLoader/SkeletonLoader"; +import VoteButton, { DOWNVOTE, UPVOTE } from "./VoteButton/VoteButton"; +import VoteCounter from "./VoteCounter/VoteCounter"; + +type TArticleViewer = { + htmlToParse?: string; +}; + +export default function ArticleViewer(props: TArticleViewer) { + let queryParams = useParams(); + const user = useAtomValue(userAtom); + + const { data, isPending } = useQuery({ + queryKey: [`post_${queryParams["postId"]}`], + queryFn: async () => { + const response = await axiosLocalhost.get( + `posts/${queryParams["postId"]}` + ); + + return response.data; + }, + gcTime: 0, + refetchOnMount: true, + }); + + if (isPending) return ; + + return ( + <> + +
+ + + + {data.title} + + + + + + + + + + + + + + + + + + + + + Add this post to blog + + + + + {`Add "${data.title}" to blog...`} + + + + + + + This + + + This is + updated blog + + + This another + + + + + + + +
+ + + +
+ + + +
+
+
+
+
+ + +
+
+
+ + ); +} diff --git a/enshi/src/Components/ArticleViewer/ChangePostButton/ChangePostButton.tsx b/enshi/src/Components/ArticleViewer/ChangePostButton/ChangePostButton.tsx new file mode 100644 index 0000000..f76cc5e --- /dev/null +++ b/enshi/src/Components/ArticleViewer/ChangePostButton/ChangePostButton.tsx @@ -0,0 +1,21 @@ +import { Button } from "@radix-ui/themes"; +import { useNavigate } from "react-router-dom"; + +type TChangePostButton = { + postId: number | string; +}; + +export default function ChangePostButton(props: TChangePostButton) { + const navigate = useNavigate(); + + return ( + + ); +} diff --git a/enshi/src/Components/ArticleViewer/SkeletonLoader/SkeletonLoader.tsx b/enshi/src/Components/ArticleViewer/SkeletonLoader/SkeletonLoader.tsx new file mode 100644 index 0000000..efa6b31 --- /dev/null +++ b/enshi/src/Components/ArticleViewer/SkeletonLoader/SkeletonLoader.tsx @@ -0,0 +1,27 @@ +import { Container, Skeleton, Text } from "@radix-ui/themes"; +import { + headerLong, + headerShort, + pText, +} from "../../../constants/textForSkeleton"; + +export default function SkeletonPostLoader() { + return ( + + + {headerLong} +
+ {headerShort} +
+
+ {pText} +
+
+ {pText} +
+
+ {pText} +
+
+ ); +} diff --git a/enshi/src/Components/ArticleViewer/VoteButton/VoteButton.tsx b/enshi/src/Components/ArticleViewer/VoteButton/VoteButton.tsx new file mode 100644 index 0000000..46a880b --- /dev/null +++ b/enshi/src/Components/ArticleViewer/VoteButton/VoteButton.tsx @@ -0,0 +1,65 @@ +import { DoubleArrowDownIcon, DoubleArrowUpIcon } from "@radix-ui/react-icons"; +import { IconButton } from "@radix-ui/themes"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { axiosLocalhost } from "../../../api/axios/axios"; + +export const UPVOTE = true; +export const DOWNVOTE = false; + +type TVoteButton = { + postId: string; + vote: boolean; +}; + +export default function VoteButton(props: TVoteButton) { + const queryClient = useQueryClient(); + + const { data } = useQuery({ + queryKey: [props.vote + "voteCheck"], + queryFn: async () => { + const response = await axiosLocalhost.get( + `post-vote/${props.postId}` + ); + + return (response.data?.vote as boolean) === props.vote || false; + }, + gcTime: 0, + }); + + const voteMutation = useMutation({ + mutationKey: [`voteMutation${props.vote}`], + onMutate: async () => { + queryClient.cancelQueries({ queryKey: [props.vote + "voteCheck"] }); + + queryClient.setQueryData([props.vote + "voteCheck"], true); + queryClient.setQueryData([!props.vote + "voteCheck"], false); + }, + mutationFn: async () => { + await axiosLocalhost.post(`post-votes/${props.postId}`, { + vote: props.vote, + }); + }, + onSuccess: () => {}, + onError: () => { + queryClient.setQueryData([props.vote + "voteCheck"], false); + }, + onSettled: () => { + queryClient.invalidateQueries({ + queryKey: [props.vote + "voteCheck"], + }); + queryClient.invalidateQueries({ + queryKey: ["post_vote_counter"], + }); + }, + }); + + return ( + voteMutation.mutate()} + > + {props.vote ? : } + + ); +} diff --git a/enshi/src/Components/ArticleViewer/VoteCounter/VoteCounter.tsx b/enshi/src/Components/ArticleViewer/VoteCounter/VoteCounter.tsx new file mode 100644 index 0000000..4d54842 --- /dev/null +++ b/enshi/src/Components/ArticleViewer/VoteCounter/VoteCounter.tsx @@ -0,0 +1,33 @@ +import { Box, Skeleton } from "@radix-ui/themes"; +import { useQuery } from "@tanstack/react-query"; +import { axiosLocalhost } from "../../../api/axios/axios"; + +type TVoteCounter = { + postId: string; +}; + +export default function VoteCounter(props: TVoteCounter) { + const { data, isLoading } = useQuery({ + queryKey: ["post_vote_counter"], + queryFn: async () => { + const response = await axiosLocalhost.get( + `post-votes/${props.postId}` + ); + return response.data as { upvotes: number; downvotes: number }; + }, + }); + + const calculateRating = (upvotes: number, downvotes: number) => { + return upvotes + (-downvotes) + } + + if (isLoading) { + return + {calculateRating(0, 0)} + + } + + return + {calculateRating(data?.upvotes || 0, data?.downvotes || 0)} + ; +} diff --git a/enshi/src/Components/BlogBox/BlogBox.tsx b/enshi/src/Components/BlogBox/BlogBox.tsx new file mode 100644 index 0000000..ff5788a --- /dev/null +++ b/enshi/src/Components/BlogBox/BlogBox.tsx @@ -0,0 +1,25 @@ +import { Avatar, Card, Flex, Heading } from "@radix-ui/themes"; +import { useNavigate } from "react-router-dom"; +import UserNicknameLink from "../UserNicknameLink/UserNicknameLink"; + +type TBlogBox = { + title?: string; + blogId?: string; + userId: string; +}; + +export default function BlogBox(props: TBlogBox) { + const navigate = useNavigate(); + + return ( + navigate(``)}> + + {props?.title || "...No title..."} + + + + + + + ); +} diff --git a/enshi/src/Components/Editor/Editor.tsx b/enshi/src/Components/Editor/Editor.tsx index d9e01a7..dd5354d 100644 --- a/enshi/src/Components/Editor/Editor.tsx +++ b/enshi/src/Components/Editor/Editor.tsx @@ -1,25 +1,19 @@ -import Quill, { Delta, } from "quill/core"; -import ReactQuill from "react-quill"; -import React, { - forwardRef, - useEffect, - useLayoutEffect, - useRef, - useState, -} from "react"; import Sources from "quill"; +import Quill, { Delta } from "quill/core"; +import { forwardRef, useEffect, useRef, useState } from "react"; +import ReactQuill from "react-quill"; type TEditor = { readOnly?: boolean; defaultValue?: string | Delta; - onChange: (d: string) => void; // TODO: make type - onSelectionChange?: any; // TODO same as before + onChange?: (d: string) => void; + onSelectionChange?: any; }; const modules = { toolbar: [ - [{ header: [1, 2, 3, false] }], - ["bold", "italic", "underline", "strike", "blockquote"], + [{ header: [1, 2, 3, 4, 5, false] }], + ["bold", "italic", "underline", "strike", "blockquote", "span-wrapper"], [ { list: "ordered" }, { list: "bullet" }, @@ -38,7 +32,9 @@ const modules = { const Editor = forwardRef((props: TEditor) => { const editor = useRef(null); const [quill, setQuill] = useState(null); - const [value, setValue] = useState(new Delta()) + const [value, setValue] = useState(new Delta()); + + const [loaded, setLoaded] = useState(false); useEffect(() => { if (editor.current) { @@ -51,13 +47,33 @@ const Editor = forwardRef((props: TEditor) => { }; }, [editor.current]); - const changeHandler = (val: string, _changeDelta: Delta, _source: Sources, _editor: ReactQuill.UnprivilegedEditor) => { + useEffect(() => { + const quill = new Quill(document.createElement("div")); + const t = quill.clipboard.convert({ + html: props.defaultValue as string, + }) as Delta; + + if (!loaded) { + setValue(t); + + console.log(t); + } + + setLoaded(true); + }, [props.defaultValue]); + + const changeHandler = ( + val: string, + _changeDelta: Delta, + _source: Sources, + _editor: ReactQuill.UnprivilegedEditor + ) => { console.log(val); - console.log(JSON.stringify(quill?.getContents().ops, null, 2)) - let fullDelta = quill?.getContents() - props.onChange(val || "") - setValue(fullDelta || new Delta()) - } + console.log(JSON.stringify(quill?.getContents().ops, null, 2)); + let fullDelta = quill?.getContents(); + if (props.onChange) props.onChange(val || ""); + if (loaded) setValue(fullDelta || new Delta()); + }; return (
@@ -65,11 +81,7 @@ const Editor = forwardRef((props: TEditor) => { value={value} ref={editor} modules={modules} - - onChange={changeHandler} - - theme="snow" placeholder="Type your thoughts here..." /> diff --git a/enshi/src/Components/NavBar/NavBar.tsx b/enshi/src/Components/NavBar/NavBar.tsx index a3d630c..17472c6 100644 --- a/enshi/src/Components/NavBar/NavBar.tsx +++ b/enshi/src/Components/NavBar/NavBar.tsx @@ -1,67 +1,15 @@ -import { Button, Card, ChevronDownIcon, Text } from "@radix-ui/themes"; -import * as NavigationMenu from "@radix-ui/react-navigation-menu"; -import { useLocation, useNavigate } from "react-router-dom"; +import CustomNavigationMenu from "./NavigationMenu/NavigationMenu"; +import RightButtonBar from "./RightButtonBar/RightButtonBar"; +import SearchField from "./SearchField/SearchField"; export default function NavBar() { return ( -