mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2025-12-31 04:00:03 +01:00
(feature): front end layout improve
This commit is contained in:
parent
1fe7c0a3da
commit
2f87b455ca
4
.gitignore
vendored
4
.gitignore
vendored
@ -262,5 +262,5 @@ __pycache__/
|
|||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
|
|
||||||
**/*docker_compose/LetsEncryptServer/acme
|
**/*docker-compose/LetsEncryptServer/acme
|
||||||
**/*docker_compose/LetsEncryptServer/cache
|
**/*docker-compose/LetsEncryptServer/cache
|
||||||
15
src/ClientApp/ApiRoutes.tsx
Normal file
15
src/ClientApp/ApiRoutes.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
enum ApiRoutes {
|
||||||
|
|
||||||
|
CACHE_GET_ACCOUNTS = `/api/Cache/GetAccounts`,
|
||||||
|
CACHE_GET_CONTACTS = `/api/Cache/GetContacts/{accountId}`,
|
||||||
|
CACHE_SET_CONTACTS = `/api/Cache/SetContacts/{accountId}`,
|
||||||
|
|
||||||
|
CERTS_FLOW_CONFIGURE_CLIENT = `/api/CertsFlow/ConfigureClient`,
|
||||||
|
CERTS_FLOW_TERMS_OF_SERVICE = `/api/CertsFlow/TermsOfService/{sessionId}`,
|
||||||
|
CERTS_FLOW_INIT = `/api/CertsFlow/Init/{sessionId}/{accountId}`,
|
||||||
|
CERTS_FLOW_NEW_ORDER = `/api/CertsFlow/NewOrder/{sessionId}`,
|
||||||
|
CERTS_FLOW_GET_ORDER = `/api/CertsFlow/GetOrder/{sessionId}`,
|
||||||
|
CERTS_FLOW_GET_CERTIFICATES = `/api/CertsFlow/GetCertificates/{sessionId}`,
|
||||||
|
CERTS_FLOW_APPLY_CERTIFICATES = `/api/CertsFlow/ApplyCertificates/{sessionId}`,
|
||||||
|
CERTS_FLOW_HOSRS_WITH_UPCOMING_SSL_EXPIRY = `/api/CertsFlow/HostsWithUpcomingSslExpiry/{sessionId}`
|
||||||
|
}
|
||||||
@ -1,11 +1,12 @@
|
|||||||
"use client"; // Add this line
|
"use client"; // Add this line
|
||||||
|
|
||||||
import React, { FC, useState } from 'react';
|
import React, { FC, useState, useEffect, useRef } from 'react';
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
import { SideMenu } from '../components/sidemenu';
|
import { SideMenu } from '../components/sidemenu';
|
||||||
import { TopMenu } from '../components/topmenu';
|
import { TopMenu } from '../components/topmenu';
|
||||||
import { Footer } from '../components/footer';
|
import { Footer } from '../components/footer';
|
||||||
import { OffCanvas } from '../components/offcanvas';
|
import { OffCanvas } from '../components/offcanvas';
|
||||||
|
import Loader from '../components/loader'; // Import the Loader component
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
|
|
||||||
const metadata: Metadata = {
|
const metadata: Metadata = {
|
||||||
@ -13,32 +14,71 @@ const metadata: Metadata = {
|
|||||||
description: "Generated by create next app",
|
description: "Generated by create next app",
|
||||||
};
|
};
|
||||||
|
|
||||||
const Layout = ({ children }: Readonly<{
|
const Layout: FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
children: React.ReactNode;
|
|
||||||
}>) => {
|
|
||||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
|
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
|
||||||
const [isOffCanvasOpen, setIsOffCanvasOpen] = useState(false);
|
const [isManuallyCollapsed, setManuallyCollapsed] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(true); // State to control the loader visibility
|
||||||
|
|
||||||
|
const init = useRef(false);
|
||||||
|
|
||||||
const toggleSidebar = () => {
|
const toggleSidebar = () => {
|
||||||
setIsSidebarCollapsed(!isSidebarCollapsed);
|
setIsSidebarCollapsed((prev) => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const manuallyToggleSidebar = () => {
|
||||||
|
setManuallyCollapsed((prev) => !prev);
|
||||||
|
toggleSidebar();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (!isManuallyCollapsed) {
|
||||||
|
if (window.innerWidth <= 768) {
|
||||||
|
if (!isSidebarCollapsed) setIsSidebarCollapsed(true);
|
||||||
|
} else {
|
||||||
|
if (isSidebarCollapsed) setIsSidebarCollapsed(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isManuallyCollapsed) return;
|
||||||
|
|
||||||
|
// Reset manualCollapse if the window is resized to a state that should automatically collapse/expand the sidebar
|
||||||
|
if (window.innerWidth > 768 && isSidebarCollapsed) {
|
||||||
|
setIsSidebarCollapsed(false);
|
||||||
|
} else if (window.innerWidth <= 768 && !isSidebarCollapsed) {
|
||||||
|
setIsSidebarCollapsed(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!init.current) {
|
||||||
|
handleResize(); // Set the initial state based on the current window width
|
||||||
|
init.current = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
setTimeout(() => setIsLoading(false), 2000); // Simulate loading for 2 seconds
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
}, [isSidebarCollapsed, isManuallyCollapsed]);
|
||||||
|
|
||||||
|
const [isOffCanvasOpen, setIsOffCanvasOpen] = useState(false);
|
||||||
|
|
||||||
const toggleOffCanvas = () => {
|
const toggleOffCanvas = () => {
|
||||||
setIsOffCanvasOpen(!isOffCanvasOpen);
|
setIsOffCanvasOpen((prev) => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
{/* <head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<link rel="icon" href="/favicon.ico" />
|
|
||||||
</head> */}
|
|
||||||
<body className="h-screen overflow-hidden">
|
<body className="h-screen overflow-hidden">
|
||||||
|
|
||||||
|
{isLoading && <Loader />} {/* Show loader if isLoading is true */}
|
||||||
|
|
||||||
<div className="flex h-full">
|
<div className="flex h-full">
|
||||||
<SideMenu isCollapsed={isSidebarCollapsed} toggleSidebar={toggleSidebar} />
|
<SideMenu isCollapsed={isSidebarCollapsed} toggleSidebar={manuallyToggleSidebar} />
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1">
|
||||||
<TopMenu onToggleOffCanvas={toggleOffCanvas} />
|
<TopMenu onToggleOffCanvas={toggleOffCanvas} />
|
||||||
<main className={`flex-1 p-4 transition-transform duration-300 ${isOffCanvasOpen ? 'transform translate-x-64' : ''}`}>
|
<main className="flex-1 p-4 transition-transform duration-300">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import React from 'react';
|
|||||||
const Footer = () => {
|
const Footer = () => {
|
||||||
return (
|
return (
|
||||||
<footer className="bg-gray-900 text-white text-center p-4">
|
<footer className="bg-gray-900 text-white text-center p-4">
|
||||||
<p>© 2024 Your Company</p>
|
<p>© {new Date().getFullYear()} MAKS-IT</p>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
13
src/ClientApp/components/loader/index.tsx
Normal file
13
src/ClientApp/components/loader/index.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import './loader.css'; // Add your loader styles here
|
||||||
|
|
||||||
|
const Loader: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="loader-overlay">
|
||||||
|
<div className="spinner"></div>
|
||||||
|
<div className="loading-text">Loading...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Loader;
|
||||||
34
src/ClientApp/components/loader/loader.css
Normal file
34
src/ClientApp/components/loader/loader.css
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
.loader-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.1); /* 10% transparent background */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 9999;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
border: 8px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-top: 8px solid #3498db;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { FC, useEffect, useRef } from 'react';
|
||||||
import { FaHome, FaUser, FaCog, FaBars } from 'react-icons/fa';
|
import { FaHome, FaUser, FaCog, FaBars } from 'react-icons/fa';
|
||||||
|
|
||||||
interface SideMenuProps {
|
interface SideMenuProps {
|
||||||
@ -7,12 +7,13 @@ interface SideMenuProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SideMenu: FC<SideMenuProps> = ({ isCollapsed, toggleSidebar }) => {
|
const SideMenu: FC<SideMenuProps> = ({ isCollapsed, toggleSidebar }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex flex-col bg-gray-800 text-white transition-all duration-300 ${isCollapsed ? 'w-16' : 'w-64'} h-full`}>
|
<div className={`flex flex-col bg-gray-800 text-white transition-all duration-300 ${isCollapsed ? 'w-16' : 'w-64'} h-full`}>
|
||||||
<div className="flex items-center h-16 bg-gray-900 relative">
|
<div className="flex items-center h-16 bg-gray-900 relative">
|
||||||
{/* <button onClick={toggleSidebar} className="absolute left-4">
|
<button onClick={toggleSidebar} className="absolute left-4">
|
||||||
<FaBars />
|
<FaBars />
|
||||||
</button> */}
|
</button>
|
||||||
<h1 className={`${isCollapsed ? 'hidden' : 'block'} text-2xl font-bold ml-12`}>Logo</h1>
|
<h1 className={`${isCollapsed ? 'hidden' : 'block'} text-2xl font-bold ml-12`}>Logo</h1>
|
||||||
</div>
|
</div>
|
||||||
<nav className="flex-1">
|
<nav className="flex-1">
|
||||||
|
|||||||
@ -17,7 +17,7 @@ const TopMenu: FC<TopMenuProps> = ({ onToggleOffCanvas }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="bg-gray-900 text-white flex items-center p-4">
|
<header className="bg-gray-900 text-white flex items-center p-4">
|
||||||
<nav className="flex-1 flex justify-between items-center">
|
<nav className="flex-1 flex justify-between items-center h-8">
|
||||||
<ul className="hidden md:flex space-x-4">
|
<ul className="hidden md:flex space-x-4">
|
||||||
<li className="hover:bg-gray-700 p-2 rounded">
|
<li className="hover:bg-gray-700 p-2 rounded">
|
||||||
<Link href="/">Home</Link>
|
<Link href="/">Home</Link>
|
||||||
@ -29,9 +29,7 @@ const TopMenu: FC<TopMenuProps> = ({ onToggleOffCanvas }) => {
|
|||||||
<Link href="/contact">Contact</Link>
|
<Link href="/contact">Contact</Link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<button onClick={toggleMenu} className="md:hidden">
|
|
||||||
<FaBars />
|
|
||||||
</button>
|
|
||||||
{isMenuOpen && (
|
{isMenuOpen && (
|
||||||
<ul className="absolute top-16 right-0 bg-gray-900 w-48 md:hidden">
|
<ul className="absolute top-16 right-0 bg-gray-900 w-48 md:hidden">
|
||||||
<li className="hover:bg-gray-700 p-2">
|
<li className="hover:bg-gray-700 p-2">
|
||||||
@ -49,6 +47,9 @@ const TopMenu: FC<TopMenuProps> = ({ onToggleOffCanvas }) => {
|
|||||||
<button onClick={onToggleOffCanvas} className="ml-4">
|
<button onClick={onToggleOffCanvas} className="ml-4">
|
||||||
<FaCog />
|
<FaCog />
|
||||||
</button>
|
</button>
|
||||||
|
<button onClick={toggleMenu} className="md:hidden">
|
||||||
|
<FaBars />
|
||||||
|
</button>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
12
src/ClientApp/http-common.tsx
Normal file
12
src/ClientApp/http-common.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { HttpService } from "./services/HttpService";
|
||||||
|
|
||||||
|
const httpService = new HttpService();
|
||||||
|
|
||||||
|
httpService.addRequestInterceptor(xhr => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
httpService.addResponseInterceptor(response => {
|
||||||
|
|
||||||
|
return response;
|
||||||
|
});
|
||||||
95
src/ClientApp/package-lock.json
generated
95
src/ClientApp/package-lock.json
generated
@ -8,10 +8,12 @@
|
|||||||
"name": "my-nextjs-app",
|
"name": "my-nextjs-app",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@reduxjs/toolkit": "^2.2.5",
|
||||||
"next": "14.2.3",
|
"next": "14.2.3",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-icons": "^5.2.1"
|
"react-icons": "^5.2.1",
|
||||||
|
"react-redux": "^9.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
@ -423,6 +425,29 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@reduxjs/toolkit": {
|
||||||
|
"version": "2.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.5.tgz",
|
||||||
|
"integrity": "sha512-aeFA/s5NCG7NoJe/MhmwREJxRkDs0ZaSqt0MxhWUrwCf1UQXpwR87RROJEql0uAkLI6U7snBOYOcKw83ew3FPg==",
|
||||||
|
"dependencies": {
|
||||||
|
"immer": "^10.0.3",
|
||||||
|
"redux": "^5.0.1",
|
||||||
|
"redux-thunk": "^3.1.0",
|
||||||
|
"reselect": "^5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.9.0 || ^17.0.0 || ^18",
|
||||||
|
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rushstack/eslint-patch": {
|
"node_modules/@rushstack/eslint-patch": {
|
||||||
"version": "1.10.3",
|
"version": "1.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.3.tgz",
|
||||||
@ -462,13 +487,13 @@
|
|||||||
"version": "15.7.12",
|
"version": "15.7.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
||||||
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
|
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
|
||||||
"dev": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.3.3",
|
"version": "18.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
|
||||||
"integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
|
"integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
@ -483,6 +508,11 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/use-sync-external-store": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz",
|
||||||
@ -1154,7 +1184,7 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"dev": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"node_modules/damerau-levenshtein": {
|
"node_modules/damerau-levenshtein": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
@ -2388,6 +2418,15 @@
|
|||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/immer": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||||
@ -3737,6 +3776,28 @@
|
|||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/react-redux": {
|
||||||
|
"version": "9.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz",
|
||||||
|
"integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/use-sync-external-store": "^0.0.3",
|
||||||
|
"use-sync-external-store": "^1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^18.2.25",
|
||||||
|
"react": "^18.0",
|
||||||
|
"redux": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
@ -3758,6 +3819,19 @@
|
|||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redux": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
|
||||||
|
},
|
||||||
|
"node_modules/redux-thunk": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"redux": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reflect.getprototypeof": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
|
||||||
@ -3803,6 +3877,11 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/reselect": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
@ -4585,6 +4664,14 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-sync-external-store": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
|||||||
@ -9,10 +9,12 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@reduxjs/toolkit": "^2.2.5",
|
||||||
"next": "14.2.3",
|
"next": "14.2.3",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-icons": "^5.2.1"
|
"react-icons": "^5.2.1",
|
||||||
|
"react-redux": "^9.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
|
|||||||
122
src/ClientApp/services/HttpService.tsx
Normal file
122
src/ClientApp/services/HttpService.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
interface RequestInterceptor {
|
||||||
|
(req: XMLHttpRequest): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResponseInterceptor<T> {
|
||||||
|
(response: T): T;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProblemDetails {
|
||||||
|
Title: string;
|
||||||
|
Detail: string | null;
|
||||||
|
Status: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class HttpService {
|
||||||
|
private requestInterceptors: Array<RequestInterceptor> = [];
|
||||||
|
private responseInterceptors: Array<ResponseInterceptor<any>> = [];
|
||||||
|
|
||||||
|
private request<TResponse>(method: string, url: string, data?: any): Promise<TResponse> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open(method, url);
|
||||||
|
|
||||||
|
// Apply request interceptors
|
||||||
|
this.requestInterceptors.forEach(interceptor => {
|
||||||
|
try {
|
||||||
|
interceptor(xhr);
|
||||||
|
} catch (error) {
|
||||||
|
reject({
|
||||||
|
Title: 'Request Interceptor Error',
|
||||||
|
Detail: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
Status: 0
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set Content-Type header for JSON data
|
||||||
|
if (data && typeof data !== 'string') {
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.onload = () => {
|
||||||
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
|
let response: TResponse;
|
||||||
|
try {
|
||||||
|
response = JSON.parse(xhr.response);
|
||||||
|
} catch (error) {
|
||||||
|
reject({
|
||||||
|
Title: 'Response Parse Error',
|
||||||
|
Detail: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
Status: xhr.status
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply response interceptors
|
||||||
|
try {
|
||||||
|
this.responseInterceptors.forEach(interceptor => {
|
||||||
|
response = interceptor(response);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
reject({
|
||||||
|
Title: 'Response Interceptor Error',
|
||||||
|
Detail: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
Status: xhr.status
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(response);
|
||||||
|
} else {
|
||||||
|
const problemDetails: ProblemDetails = {
|
||||||
|
Title: xhr.statusText,
|
||||||
|
Detail: xhr.responseText,
|
||||||
|
Status: xhr.status
|
||||||
|
};
|
||||||
|
reject(problemDetails);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = () => {
|
||||||
|
const problemDetails: ProblemDetails = {
|
||||||
|
Title: 'Network Error',
|
||||||
|
Detail: null,
|
||||||
|
Status: 0
|
||||||
|
};
|
||||||
|
reject(problemDetails);
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.send(data ? JSON.stringify(data) : null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public get<TResponse>(url: string): Promise<TResponse | ProblemDetails> {
|
||||||
|
return this.request<TResponse>('GET', url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public post<TRequest, TResponse>(url: string, data: TRequest): Promise<TResponse | ProblemDetails> {
|
||||||
|
return this.request<TResponse>('POST', url, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public put<TRequest, TResponse>(url: string, data: TRequest): Promise<TResponse | ProblemDetails> {
|
||||||
|
return this.request<TResponse>('PUT', url, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public delete<TResponse>(url: string): Promise<TResponse | ProblemDetails> {
|
||||||
|
return this.request<TResponse>('DELETE', url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addRequestInterceptor(interceptor: RequestInterceptor): void {
|
||||||
|
this.requestInterceptors.push(interceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addResponseInterceptor<TResponse>(interceptor: ResponseInterceptor<TResponse | ProblemDetails>): void {
|
||||||
|
this.responseInterceptors.push(interceptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
HttpService
|
||||||
|
};
|
||||||
@ -27,6 +27,12 @@ public class CacheController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpGet("[action]")]
|
||||||
|
public async Task<IActionResult> GetAccounts() {
|
||||||
|
var result = await _cacheService.ListCachedAccountsAsync();
|
||||||
|
return result.ToActionResult();
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("[action]/{accountId}")]
|
[HttpGet("[action]/{accountId}")]
|
||||||
public async Task<IActionResult> GetContacts(Guid accountId) {
|
public async Task<IActionResult> GetContacts(Guid accountId) {
|
||||||
var result = await _cacheService.GetContactsAsync(accountId);
|
var result = await _cacheService.GetContactsAsync(accountId);
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace MaksIT.LetsEncryptServer.Middlewares {
|
||||||
|
public class GlobalExceptionMiddleware {
|
||||||
|
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly ILogger<GlobalExceptionMiddleware> _logger;
|
||||||
|
|
||||||
|
public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger) {
|
||||||
|
_next = next;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeAsync(HttpContext context) {
|
||||||
|
try {
|
||||||
|
await _next(context);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
_logger.LogError(ex, "An unhandled exception occurred.");
|
||||||
|
await HandleExceptionAsync(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task HandleExceptionAsync(HttpContext context) {
|
||||||
|
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||||
|
context.Response.ContentType = "application/json";
|
||||||
|
|
||||||
|
var response = new { message = "An error occurred while processing your request." };
|
||||||
|
return context.Response.WriteAsJsonAsync(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ using MaksIT.LetsEncryptServer;
|
|||||||
using MaksIT.LetsEncrypt.Services;
|
using MaksIT.LetsEncrypt.Services;
|
||||||
using MaksIT.LetsEncryptServer.Services;
|
using MaksIT.LetsEncryptServer.Services;
|
||||||
using MaksIT.LetsEncryptServer.BackgroundServices;
|
using MaksIT.LetsEncryptServer.BackgroundServices;
|
||||||
|
using MaksIT.LetsEncryptServer.Middlewares;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@ -23,6 +24,8 @@ builder.Services.AddControllers();
|
|||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
builder.Services.AddCors();
|
||||||
|
|
||||||
builder.Services.AddMemoryCache();
|
builder.Services.AddMemoryCache();
|
||||||
|
|
||||||
builder.Services.AddHttpClient<ILetsEncryptService, LetsEncryptService>();
|
builder.Services.AddHttpClient<ILetsEncryptService, LetsEncryptService>();
|
||||||
@ -37,7 +40,13 @@ var app = builder.Build();
|
|||||||
if (app.Environment.IsDevelopment()) {
|
if (app.Environment.IsDevelopment()) {
|
||||||
app.UseSwagger();
|
app.UseSwagger();
|
||||||
app.UseSwaggerUI();
|
app.UseSwaggerUI();
|
||||||
|
app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// app.UseMiddleware<GlobalExceptionMiddleware>();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseMiddleware<GlobalExceptionMiddleware>();
|
||||||
|
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
version: '3.9'
|
version: '3.9'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
haproxy:
|
# haproxy:
|
||||||
ports:
|
# ports:
|
||||||
- "8080:8080"
|
# - "8080:8080"
|
||||||
volumes:
|
# volumes:
|
||||||
- ./docker-compose/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
|
# - ./docker-compose/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
|
||||||
depends_on:
|
# depends_on:
|
||||||
- letsencryptapp
|
# - letsencryptapp
|
||||||
- letsencryptserver
|
# - letsencryptserver
|
||||||
|
|
||||||
letsencryptapp:
|
# letsencryptapp:
|
||||||
ports:
|
# ports:
|
||||||
- "3000:3000"
|
# - "3000:3000"
|
||||||
|
|
||||||
letsencryptserver:
|
letsencryptserver:
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@ -2,14 +2,14 @@ version: '3.9'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
haproxy:
|
# haproxy:
|
||||||
image: haproxy:3.0.0-alpine
|
# image: haproxy:3.0.0-alpine
|
||||||
|
|
||||||
letsencryptapp:
|
# letsencryptapp:
|
||||||
image: ${DOCKER_REGISTRY-}letsencryptapp
|
# image: ${DOCKER_REGISTRY-}letsencryptapp
|
||||||
build:
|
# build:
|
||||||
context: .
|
# context: .
|
||||||
dockerfile: ClientApp/Dockerfile
|
# dockerfile: ClientApp/Dockerfile
|
||||||
|
|
||||||
letsencryptserver:
|
letsencryptserver:
|
||||||
image: ${DOCKER_REGISTRY-}letsencryptserver
|
image: ${DOCKER_REGISTRY-}letsencryptserver
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 3000;
|
|
||||||
|
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
index index.html index.htm;
|
|
||||||
try_files $uri $uri/ /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
|
||||||
location = /50x.html {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
VTpgCMVyk7RMm0qKEURrgcfHI5Y0RKRWlF0Up1y1xMs.CmDayuKv1cGaB2xr6W5cZk_Jqbyonzm29xtXVNgBMAQ
|
|
||||||
@ -1 +0,0 @@
|
|||||||
sf6oq1qazlokoWqGJam03udFEYOanTus2DmShjnfAcw.CmDayuKv1cGaB2xr6W5cZk_Jqbyonzm29xtXVNgBMAQ
|
|
||||||
Loading…
Reference in New Issue
Block a user