auth
parent
5b19897740
commit
12467edda7
|
@ -0,0 +1,7 @@
|
||||||
|
git add *
|
||||||
|
|
||||||
|
read -p "Commit message: " commit_message
|
||||||
|
|
||||||
|
git commit -m "$commit_message"
|
||||||
|
|
||||||
|
git push -u origin main
|
|
@ -19,6 +19,7 @@
|
||||||
"@types/react": "^18.3.4",
|
"@types/react": "^18.3.4",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"antd": "^5.20.3",
|
"antd": "^5.20.3",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
"i18next": "^23.7.6",
|
"i18next": "^23.7.6",
|
||||||
"i18next-browser-languagedetector": "^7.2.0",
|
"i18next-browser-languagedetector": "^7.2.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
@ -6293,6 +6294,26 @@
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/base64-js": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/batch": {
|
"node_modules/batch": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
||||||
|
@ -6487,6 +6508,30 @@
|
||||||
"node-int64": "^0.4.0"
|
"node-int64": "^0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.3.1",
|
||||||
|
"ieee754": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/buffer-from": {
|
"node_modules/buffer-from": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
|
@ -10403,6 +10448,26 @@
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ieee754": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
"@types/react": "^18.3.4",
|
"@types/react": "^18.3.4",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"antd": "^5.20.3",
|
"antd": "^5.20.3",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
"i18next": "^23.7.6",
|
"i18next": "^23.7.6",
|
||||||
"i18next-browser-languagedetector": "^7.2.0",
|
"i18next-browser-languagedetector": "^7.2.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
|
38
src/App.css
38
src/App.css
|
@ -1,38 +0,0 @@
|
||||||
.App {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-logo {
|
|
||||||
height: 40vmin;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
.App-logo {
|
|
||||||
animation: App-logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-header {
|
|
||||||
background-color: #282c34;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: calc(10px + 2vmin);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-link {
|
|
||||||
color: #61dafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes App-logo-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
import React from 'react';
|
|
||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
|
||||||
|
|
56
src/App.tsx
56
src/App.tsx
|
@ -1,29 +1,65 @@
|
||||||
import React from "react";
|
import { ConfigProvider, Layout, theme } from "antd";
|
||||||
import logo from "./logo.svg";
|
|
||||||
import "./App.css";
|
|
||||||
import { Button, ConfigProvider, Layout, theme } from "antd";
|
|
||||||
import DashboardLayout from "./core/components/DashboardLayout";
|
import DashboardLayout from "./core/components/DashboardLayout";
|
||||||
import { darkMode } from "./core/store/appSlice";
|
import {
|
||||||
import { useSelector } from "react-redux";
|
darkMode,
|
||||||
|
setUserAuthenticated,
|
||||||
|
userAuthenticated,
|
||||||
|
} from "./core/store/appSlice";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import SignIn from "./features/Auth/SignIn";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { myFetch } from "./core/utils/utils";
|
||||||
|
import MyCenteredSpin from "./shared/components/MyCenteredSpin";
|
||||||
|
|
||||||
const { defaultAlgorithm, darkAlgorithm } = theme;
|
const { defaultAlgorithm, darkAlgorithm } = theme;
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const isDarkMode = useSelector(darkMode)
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const isDarkMode = useSelector(darkMode);
|
||||||
|
const uAuthenticated = useSelector(userAuthenticated);
|
||||||
|
|
||||||
console.info(
|
console.info(
|
||||||
"\n %c LMS %c v0.1.0 %c \n",
|
"\n %c LMS %c v1.0.0 %c \n",
|
||||||
"background-color: #555;color: #fff;padding: 3px 2px 3px 3px;border-radius: 3px 0 0 3px;font-family: DejaVu Sans,Verdana,Geneva,sans-serif;text-shadow: 0 1px 0 rgba(1, 1, 1, 0.3)",
|
"background-color: #555;color: #fff;padding: 3px 2px 3px 3px;border-radius: 3px 0 0 3px;font-family: DejaVu Sans,Verdana,Geneva,sans-serif;text-shadow: 0 1px 0 rgba(1, 1, 1, 0.3)",
|
||||||
"background-color: #bc81e0;background-image: linear-gradient(90deg, #e67e22, #9b59b6);color: #fff;padding: 3px 3px 3px 2px;border-radius: 0 3px 3px 0;font-family: DejaVu Sans,Verdana,Geneva,sans-serif;text-shadow: 0 1px 0 rgba(1, 1, 1, 0.3)",
|
"background-color: #bc81e0;background-image: linear-gradient(90deg, #e67e22, #9b59b6);color: #fff;padding: 3px 3px 3px 2px;border-radius: 0 3px 3px 0;font-family: DejaVu Sans,Verdana,Geneva,sans-serif;text-shadow: 0 1px 0 rgba(1, 1, 1, 0.3)",
|
||||||
"background-color: transparent"
|
"background-color: transparent"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!localStorage.getItem("session")) {
|
||||||
|
dispatch(setUserAuthenticated(false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const response = await myFetch({
|
||||||
|
url: "/user",
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
dispatch(setUserAuthenticated(true))
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ minHeight: "100vh" }}>
|
<Layout style={{ minHeight: "100vh" }}>
|
||||||
<ConfigProvider theme={{
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
|
{uAuthenticated == null ? (
|
||||||
|
<MyCenteredSpin />
|
||||||
|
) : uAuthenticated ? (
|
||||||
<DashboardLayout />
|
<DashboardLayout />
|
||||||
|
) : (
|
||||||
|
<SignIn />
|
||||||
|
)}
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,7 +14,6 @@ import {
|
||||||
SunOutlined,
|
SunOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { editorActive } from "../../../features/Lessons/LessonPageEditor/lessonPageEditorSlice";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { darkMode, setDarkMode } from "../../store/appSlice";
|
import { darkMode, setDarkMode } from "../../store/appSlice";
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import { Layout } from "antd";
|
import { Layout } from "antd";
|
||||||
import { Content } from "antd/es/layout/layout";
|
|
||||||
import HeaderMenu from "../Header";
|
|
||||||
import AppRoutes from "../AppRoutes";
|
import AppRoutes from "../AppRoutes";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { isSideMenuCollapsed } from "../SideMenu/sideMenuSlice";
|
import { isSideMenuCollapsed } from "../SideMenu/sideMenuSlice";
|
||||||
|
@ -18,8 +16,6 @@ export default function PageContent() {
|
||||||
: 200,
|
: 200,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|
||||||
<AppRoutes />
|
<AppRoutes />
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import {
|
import {
|
||||||
ControlOutlined,
|
ControlOutlined,
|
||||||
GroupOutlined,
|
|
||||||
MessageOutlined,
|
MessageOutlined,
|
||||||
PieChartOutlined,
|
|
||||||
QuestionCircleOutlined,
|
QuestionCircleOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
SnippetsOutlined,
|
SnippetsOutlined,
|
||||||
|
@ -12,7 +10,6 @@ import {
|
||||||
import { Divider, Flex, Menu } from "antd";
|
import { Divider, Flex, Menu } from "antd";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {
|
import {
|
||||||
setIsSideMenuCollapsed,
|
setIsSideMenuCollapsed,
|
||||||
|
@ -21,7 +18,6 @@ import {
|
||||||
} from "./sideMenuSlice";
|
} from "./sideMenuSlice";
|
||||||
import { ItemType, MenuItemType } from "antd/es/menu/interface";
|
import { ItemType, MenuItemType } from "antd/es/menu/interface";
|
||||||
import { BreakpointLgWidth, Constants } from "../../utils/utils";
|
import { BreakpointLgWidth, Constants } from "../../utils/utils";
|
||||||
import { useDraggable, useDroppable } from "@dnd-kit/core";
|
|
||||||
import Search from "antd/es/input/Search";
|
import Search from "antd/es/input/Search";
|
||||||
import { MyContainer } from "../../../shared/components/MyContainer";
|
import { MyContainer } from "../../../shared/components/MyContainer";
|
||||||
import { addLessonContent } from "../../../features/Lessons/LessonPageEditor/lessonPageEditorSlice";
|
import { addLessonContent } from "../../../features/Lessons/LessonPageEditor/lessonPageEditorSlice";
|
||||||
|
@ -30,7 +26,6 @@ export function SideMenuContent() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [selectedKeys, setSelectedKeys] = useState("/");
|
const [selectedKeys, setSelectedKeys] = useState("/");
|
||||||
const [openKeys, setOpenKeys] = useState([""]);
|
const [openKeys, setOpenKeys] = useState([""]);
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const componentFirstRender = useSelector(sideMenuComponentFirstRender);
|
const componentFirstRender = useSelector(sideMenuComponentFirstRender);
|
||||||
|
|
|
@ -4,17 +4,22 @@ export const appSlice = createSlice({
|
||||||
name: "app",
|
name: "app",
|
||||||
initialState: {
|
initialState: {
|
||||||
darkMode: false,
|
darkMode: false,
|
||||||
|
userAuthenticated: null,
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
setDarkMode: (state, action) => {
|
setDarkMode: (state, action) => {
|
||||||
state.darkMode = action.payload;
|
state.darkMode = action.payload;
|
||||||
},
|
},
|
||||||
|
setUserAuthenticated: (state, action) => {
|
||||||
|
state.userAuthenticated = action.payload;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
selectors: {
|
selectors: {
|
||||||
darkMode: (state) => state.darkMode,
|
darkMode: (state) => state.darkMode,
|
||||||
|
userAuthenticated: (state) => state.userAuthenticated,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const { setDarkMode } = appSlice.actions;
|
export const { setDarkMode, setUserAuthenticated } = appSlice.actions;
|
||||||
|
|
||||||
export const { darkMode } = appSlice.selectors;
|
export const { darkMode, userAuthenticated } = appSlice.selectors;
|
|
@ -1,4 +1,7 @@
|
||||||
|
import { Buffer } from "buffer";
|
||||||
|
|
||||||
export const Constants = {
|
export const Constants = {
|
||||||
|
API_ADDRESS: `${window.location.protocol}//${window.location.hostname}/api/v1`,
|
||||||
ROUTE_PATHS: {
|
ROUTE_PATHS: {
|
||||||
LESSIONS: {
|
LESSIONS: {
|
||||||
ROOT: "/lessons",
|
ROOT: "/lessons",
|
||||||
|
@ -12,7 +15,150 @@ export const Constants = {
|
||||||
SUGGEST_FEATURE: "/suggest-feature",
|
SUGGEST_FEATURE: "/suggest-feature",
|
||||||
CONTACT_SUPPORT: "/contact-support",
|
CONTACT_SUPPORT: "/contact-support",
|
||||||
},
|
},
|
||||||
|
STYLES: {
|
||||||
|
BLACK: "#000",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// used for sideMenu
|
// used for sideMenu
|
||||||
export const BreakpointLgWidth = 992;
|
export const BreakpointLgWidth = 992;
|
||||||
|
|
||||||
|
export function getUserSessionFromLocalStorage() {
|
||||||
|
return localStorage.getItem("session");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EncodeStringToBase64(value: string) {
|
||||||
|
return Buffer.from(value).toString("base64");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DecodedBase64ToString(value: string) {
|
||||||
|
return Buffer.from(value, "base64").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleLogout() {
|
||||||
|
localStorage.removeItem("session");
|
||||||
|
window.location.href = "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
export const myFetchContentType = {
|
||||||
|
JSON: 0,
|
||||||
|
FORM_DATA: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||||
|
|
||||||
|
interface MyFetchOptions<TRequest = any, TResponse = any> {
|
||||||
|
url?: string;
|
||||||
|
method?: Method;
|
||||||
|
body?: TRequest | null;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
contentType?: "JSON" | "FORM_DATA";
|
||||||
|
fetchUrl?: string;
|
||||||
|
ignoreUnauthorized?: boolean;
|
||||||
|
notificationApi?: any; // Passen Sie dies je nach Bedarf an
|
||||||
|
t?: any; // Passen Sie dies je nach Bedarf an
|
||||||
|
}
|
||||||
|
|
||||||
|
export function myFetch<TRequest = any, TResponse = any>({
|
||||||
|
url = "",
|
||||||
|
method = "GET",
|
||||||
|
body = null,
|
||||||
|
headers = {},
|
||||||
|
contentType = "JSON",
|
||||||
|
fetchUrl = Constants.API_ADDRESS,
|
||||||
|
ignoreUnauthorized = false,
|
||||||
|
notificationApi = null,
|
||||||
|
t = null,
|
||||||
|
}: MyFetchOptions<TRequest, TResponse>): Promise<TResponse> {
|
||||||
|
const getContentType = () => {
|
||||||
|
if (contentType === "JSON") return "application/json";
|
||||||
|
|
||||||
|
return "multipart/form-data";
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBody = () => {
|
||||||
|
if (!body) return null;
|
||||||
|
|
||||||
|
if (contentType === "JSON") return JSON.stringify(body);
|
||||||
|
|
||||||
|
return body;
|
||||||
|
};
|
||||||
|
|
||||||
|
// abort fetch if it takes to long
|
||||||
|
const controller = new AbortController();
|
||||||
|
const signal = controller.signal;
|
||||||
|
|
||||||
|
setTimeout(() => controller.abort(), 30000); // 30 seconds
|
||||||
|
|
||||||
|
const requestOptions = {
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
"X-Authorization": getUserSessionFromLocalStorage() || "",
|
||||||
|
"Content-Type": getContentType(),
|
||||||
|
...headers,
|
||||||
|
},
|
||||||
|
body: getBody(),
|
||||||
|
signal: signal,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fetchUrl === "") {
|
||||||
|
fetchUrl = Constants.API_ADDRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(`${fetchUrl}${url}`, requestOptions as RequestInit)
|
||||||
|
.then(async (response) => {
|
||||||
|
// if status is not in range 200-299
|
||||||
|
if (!response.ok) {
|
||||||
|
if (!ignoreUnauthorized && response.status === 401) {
|
||||||
|
console.error("Unauthorized");
|
||||||
|
// TODO: check here
|
||||||
|
//setUserSessionToLocalStorage("");
|
||||||
|
//window.location.href = "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
throw response.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if response is json
|
||||||
|
if (response.headers.get("content-type")?.includes("application/json")) {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.catch(async (error) => {
|
||||||
|
console.error("Error", error);
|
||||||
|
|
||||||
|
// ignore errors here as they are handled in the components
|
||||||
|
if (error === 400) throw error;
|
||||||
|
|
||||||
|
if (error === 401) {
|
||||||
|
if (ignoreUnauthorized) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLogout();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// show error notification for all other errors
|
||||||
|
|
||||||
|
if (notificationApi !== null && t !== null) {
|
||||||
|
if (error === 500) {
|
||||||
|
/* notificationApi["error"]({
|
||||||
|
message: t("common.request.failed.title"),
|
||||||
|
description: t("common.request.failed.description"),
|
||||||
|
}); */
|
||||||
|
} else {
|
||||||
|
// other errors
|
||||||
|
/* notificationApi["error"]({
|
||||||
|
message: t("common.request.failedInternetProblem.title"),
|
||||||
|
description: t("common.request.failedInternetProblem.description"),
|
||||||
|
}); */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
import { Button, Card, ConfigProvider, Flex, Form, Input } from "antd";
|
||||||
|
import { useForm } from "antd/es/form/Form";
|
||||||
|
import styles from "./styles.module.css";
|
||||||
|
import {
|
||||||
|
Constants,
|
||||||
|
EncodeStringToBase64,
|
||||||
|
myFetch,
|
||||||
|
} from "../../../core/utils/utils";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { setUserAuthenticated } from "../../../core/store/appSlice";
|
||||||
|
|
||||||
|
type FieldType = {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SignInFetchResponse {
|
||||||
|
Session: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SignIn() {
|
||||||
|
const [form] = useForm();
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const [isRequesting, setIsRequesting] = useState(false);
|
||||||
|
|
||||||
|
const handleSignIn = async (values: FieldType) => {
|
||||||
|
setIsRequesting(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await myFetch<FieldType, SignInFetchResponse>({
|
||||||
|
url: "/user/auth/login",
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
email: values.email,
|
||||||
|
password: EncodeStringToBase64(values.password),
|
||||||
|
},
|
||||||
|
ignoreUnauthorized: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsRequesting(false);
|
||||||
|
|
||||||
|
localStorage.setItem("session", response.Session);
|
||||||
|
|
||||||
|
dispatch(setUserAuthenticated(true));
|
||||||
|
} catch (error) {
|
||||||
|
console.log("here", error);
|
||||||
|
setIsRequesting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.backgroundImage} />
|
||||||
|
<div className={styles.darkenBackground} />
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
justify="center"
|
||||||
|
align="center"
|
||||||
|
style={{ position: "absolute", height: "100vh", width: "100%" }}
|
||||||
|
>
|
||||||
|
<Card style={{ width: 450, margin: 20 }}>
|
||||||
|
<h1 style={{ marginTop: 0 }}>Sign In</h1>
|
||||||
|
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
token: {
|
||||||
|
colorPrimary: Constants.STYLES.BLACK,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
requiredMark={false}
|
||||||
|
onFinish={handleSignIn}
|
||||||
|
>
|
||||||
|
<Form.Item<FieldType>
|
||||||
|
label="Email"
|
||||||
|
name="email"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
type: "email",
|
||||||
|
message: "Please input your email!",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="Email" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item<FieldType>
|
||||||
|
label="Password"
|
||||||
|
name="password"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: "Please input your password!" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input.Password placeholder="Password" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item style={{ marginBottom: 0 }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
block
|
||||||
|
loading={isRequesting}
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</ConfigProvider>
|
||||||
|
</Card>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
|
@ -0,0 +1,18 @@
|
||||||
|
.backgroundImage {
|
||||||
|
position: absolute;
|
||||||
|
background-image: url("pexels-photo-380769.webp");
|
||||||
|
filter: blur(8px);
|
||||||
|
-webkit-filter: blur(8px);
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.darkenBackground {
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
|
@ -3,8 +3,6 @@ import { Button, Card, Flex, Typography } from "antd";
|
||||||
import img from "./pexels-photo-302902.webp";
|
import img from "./pexels-photo-302902.webp";
|
||||||
import { MyContainer } from "../../../shared/components/MyContainer";
|
import { MyContainer } from "../../../shared/components/MyContainer";
|
||||||
import { CheckOutlined } from "@ant-design/icons";
|
import { CheckOutlined } from "@ant-design/icons";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
import { lessonContents } from "../LessonPageEditor/lessonPageEditorSlice";
|
|
||||||
import HeaderBar from "../../../core/components/Header";
|
import HeaderBar from "../../../core/components/Header";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { Constants } from "../../../core/utils/utils";
|
import { Constants } from "../../../core/utils/utils";
|
||||||
|
@ -103,7 +101,7 @@ export function Converter({
|
||||||
{lessonContent.data}
|
{lessonContent.data}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case 3:
|
case 4:
|
||||||
return <div style={{ fontSize: 14 }}>{lessonContent.data}</div>;
|
return <div style={{ fontSize: 14 }}>{lessonContent.data}</div>;
|
||||||
default:
|
default:
|
||||||
return <div>Unknown type</div>;
|
return <div>Unknown type</div>;
|
||||||
|
|
|
@ -1,21 +1,25 @@
|
||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { store } from "./core/store/store";
|
import { store } from "./core/store/store";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import MyCenteredSpin from "./shared/components/MyCenteredSpin";
|
||||||
// import reportWebVitals from './reportWebVitals';
|
// import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(
|
||||||
document.getElementById("root") as HTMLElement
|
document.getElementById("root") as HTMLElement
|
||||||
);
|
);
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
|
<Suspense fallback={<MyCenteredSpin />}>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<App />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
// If you want to start measuring performance in your app, pass a function
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Spin } from "antd";
|
|
||||||
import { MyCenteredContainer } from "../MyContainer";
|
import { MyCenteredContainer } from "../MyContainer";
|
||||||
|
import MySpin from "../MySpin";
|
||||||
|
|
||||||
export default function MyCenteredSpin({ fullHeight = false }) {
|
export default function MyCenteredSpin({ fullHeight = false }) {
|
||||||
return (
|
return (
|
||||||
<MyCenteredContainer fullHeight>
|
<MyCenteredContainer fullHeight>
|
||||||
<Spin size="large" />
|
<MySpin />
|
||||||
</MyCenteredContainer>
|
</MyCenteredContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { LoadingOutlined } from "@ant-design/icons";
|
||||||
|
import { Spin } from "antd";
|
||||||
|
|
||||||
|
export default function MySpin() {
|
||||||
|
return <Spin size="large" indicator={<LoadingOutlined spin />} />;
|
||||||
|
}
|
Loading…
Reference in New Issue