Compare commits
10 Commits
84a7d0583a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c500ddbe00 | ||
|
|
8c6d6ae5e2 | ||
|
|
03919f7b97 | ||
|
|
67f56c823e | ||
|
|
041c4d0820 | ||
|
|
bf862a52a7 | ||
|
|
425655ad3c | ||
|
|
e2a1dc6001 | ||
|
|
d2df32d392 | ||
|
|
4824d831e3 |
@@ -1,10 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||||
import { Auth, Login, Logout, NavBar, Overview } from './components';
|
import { Auth, Login, Logout, NavBar, Overview } from './components';
|
||||||
import { AuthProvider, TimeProvider } from './contexts';
|
import { AuthProvider, ThemeProvider, TimeProvider } from './contexts';
|
||||||
import './App.css';
|
|
||||||
import { Airport } from './components/airport';
|
import { Airport } from './components/airport';
|
||||||
import { ThemeProvider } from './contexts/themecontext';
|
import './App.css';
|
||||||
|
|
||||||
const App: React.FC = () => (
|
const App: React.FC = () => (
|
||||||
<>
|
<>
|
||||||
|
|||||||
19
src/components/airport.tsx
Normal file
19
src/components/airport.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { TabView, TabPanel } from 'primereact/tabview';
|
||||||
|
|
||||||
|
export const Airport: React.FC = () => {
|
||||||
|
const [activeIndex, setActiveIndex] = useState<number>(0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TabView activeIndex={activeIndex} onTabChange={(event) => setActiveIndex(event.index)} className='p-3'>
|
||||||
|
<TabPanel header='Create airport'>
|
||||||
|
CREATE AIRPORT
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel header='Manage airport'>
|
||||||
|
MANAGE AIRPORT
|
||||||
|
</TabPanel>
|
||||||
|
</TabView>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,17 +1,29 @@
|
|||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
|
import { AuthContext } from '../contexts';
|
||||||
import { Session } from '../services';
|
import { Session } from '../services';
|
||||||
|
|
||||||
export const Auth: React.FC = () => {
|
export const Auth: React.FC = () => {
|
||||||
|
const { reloadAuth } = useContext(AuthContext);
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const token = searchParams.get('token');
|
const token = searchParams.get('token');
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const baseUrl = `${window.location.protocol}//${window.location.host}`
|
const baseUrl = `${window.location.protocol}//${window.location.host}`
|
||||||
if (token) {
|
if (token) {
|
||||||
Session.setBearerToken(token);
|
Session.setBearerToken(token);
|
||||||
window.location.replace(`${baseUrl}/overview`);
|
|
||||||
|
const lastRoute = Session.lastShownComponent();
|
||||||
|
if (lastRoute !== null) {
|
||||||
|
reloadAuth();
|
||||||
|
Session.resetLastShownComponent();
|
||||||
|
navigate(lastRoute);
|
||||||
|
} else {
|
||||||
|
window.location.replace(`${baseUrl}/overview`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Session.reset();
|
Session.resetLastShownComponent();
|
||||||
|
Session.resetBearerToken();
|
||||||
window.location.replace(`${baseUrl}/`);
|
window.location.replace(`${baseUrl}/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ export const Login: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Auth.tokenIsValid().then((status) => {
|
Auth.tokenIsValid().then((status) => {
|
||||||
if (status !== BackendReturnStatus.Ok) {
|
if (status !== BackendReturnStatus.Ok) {
|
||||||
Session.reset();
|
Session.resetLastShownComponent();
|
||||||
|
Session.resetBearerToken();
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} else {
|
} else {
|
||||||
navigate('/overview');
|
navigate('/overview');
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ export const Logout: React.FC = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Session.reset();
|
Session.resetLastShownComponent();
|
||||||
|
Session.resetBearerToken();
|
||||||
context.resetAuth();
|
context.resetAuth();
|
||||||
navigate('/');
|
navigate('/');
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import { Menubar } from 'primereact/menubar';
|
import { Menubar } from 'primereact/menubar';
|
||||||
|
import { MenuItem } from 'primereact/menuitem';
|
||||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { AuthContext, TimeContext } from '../contexts';
|
import { AuthContext, ThemeContext } from '../contexts';
|
||||||
import { Configuration } from '../services';
|
import { Configuration } from '../services';
|
||||||
import { Airport } from '../services';
|
import { Airport } from '../services';
|
||||||
import { AirportOverview, BackendReturnStatus, IAuthState } from '../types';
|
import { AirportOverview, BackendReturnStatus, IAuthState } from '../types';
|
||||||
|
|
||||||
export const NavBar: React.FC = () => {
|
export const NavBar: React.FC = () => {
|
||||||
const [timestamp, setTimestamp] = useState<string>('');
|
|
||||||
const [fullName, setFullName] = useState<string>('');
|
|
||||||
const [menuTree, setMenuTree] = useState<any>(undefined);
|
const [menuTree, setMenuTree] = useState<any>(undefined);
|
||||||
|
const themeContext = useContext(ThemeContext);
|
||||||
const authContext = useContext(AuthContext);
|
const authContext = useContext(AuthContext);
|
||||||
const timeContext = useContext(TimeContext);
|
|
||||||
const currentAuth = useRef<IAuthState>();
|
const currentAuth = useRef<IAuthState>();
|
||||||
|
const currentTheme = useRef<boolean>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
currentTheme.current = themeContext.darkMode;
|
||||||
currentAuth.current = authContext.auth;
|
currentAuth.current = authContext.auth;
|
||||||
|
|
||||||
const firBasedSubMenu = (airports: AirportOverview[], endpoint: string): any[] => {
|
const firBasedSubMenu = (airports: AirportOverview[], endpoint: string): any[] => {
|
||||||
@@ -66,10 +67,10 @@ export const NavBar: React.FC = () => {
|
|||||||
Airport.all().then((response) => {
|
Airport.all().then((response) => {
|
||||||
if (response.status !== BackendReturnStatus.Ok) return [];
|
if (response.status !== BackendReturnStatus.Ok) return [];
|
||||||
|
|
||||||
const newMenuTree: { label: string; items?: any[]; command?: () => void }[] = [
|
const newMenuTree: MenuItem[] = [
|
||||||
{
|
{
|
||||||
label: 'Airports',
|
label: 'Airports',
|
||||||
items: [] as any[],
|
items: [],
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -106,6 +107,14 @@ export const NavBar: React.FC = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newMenuTree.push({
|
||||||
|
label: currentTheme.current ? 'Light mode' : 'Dark mode',
|
||||||
|
command: () => {
|
||||||
|
themeContext.setDarkMode(!currentTheme.current);
|
||||||
|
updateMenuItems();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
newMenuTree.push({
|
newMenuTree.push({
|
||||||
label: 'Logout',
|
label: 'Logout',
|
||||||
command: () => navigate('/logout'),
|
command: () => navigate('/logout'),
|
||||||
@@ -121,18 +130,7 @@ export const NavBar: React.FC = () => {
|
|||||||
updateMenuItems();
|
updateMenuItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeInterval = setInterval(() => {
|
|
||||||
const serverUtcTime = new Date(new Date().getTime() + timeContext.offset);
|
|
||||||
const hours = String(serverUtcTime.getUTCHours()).padStart(2, '0');
|
|
||||||
const minutes = String(serverUtcTime.getUTCMinutes()).padStart(2, '0');
|
|
||||||
const seconds = String(serverUtcTime.getUTCSeconds()).padStart(2, '0');
|
|
||||||
if (currentAuth.current?.valid) {
|
|
||||||
setTimestamp(`${hours}:${minutes}:${seconds}`);
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(timeInterval);
|
|
||||||
event.close();
|
event.close();
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -140,14 +138,8 @@ export const NavBar: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentAuth.current?.valid) {
|
if (currentAuth.current?.valid) {
|
||||||
if (currentAuth.current.user.fullName !== '') {
|
|
||||||
setFullName(currentAuth.current.user.fullName);
|
|
||||||
} else {
|
|
||||||
setFullName(currentAuth.current.user.vatsimId);
|
|
||||||
}
|
|
||||||
updateMenuItems();
|
updateMenuItems();
|
||||||
} else {
|
} else {
|
||||||
setFullName('');
|
|
||||||
setMenuTree([]);
|
setMenuTree([]);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -155,9 +147,9 @@ export const NavBar: React.FC = () => {
|
|||||||
|
|
||||||
if (menuTree === undefined || !currentAuth.current.valid) return <></>;
|
if (menuTree === undefined || !currentAuth.current.valid) return <></>;
|
||||||
|
|
||||||
const rightSideInfo = (
|
return (
|
||||||
<div>{fullName} | {timestamp}</div>
|
menuTree.length !== 0 ?
|
||||||
|
<Menubar model={menuTree} />
|
||||||
|
: <></>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (menuTree.length !== 0 ? <Menubar model={menuTree} end={rightSideInfo} /> : <></>);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Card } from 'primereact/card';
|
|||||||
import { DataTable } from 'primereact/datatable';
|
import { DataTable } from 'primereact/datatable';
|
||||||
import { Password } from 'primereact/password';
|
import { Password } from 'primereact/password';
|
||||||
import { AuthContext } from '../contexts';
|
import { AuthContext } from '../contexts';
|
||||||
import { Auth } from '../services';
|
import { Auth, Session } from '../services';
|
||||||
import { BackendReturnStatus } from '../types';
|
import { BackendReturnStatus } from '../types';
|
||||||
import { Column } from 'primereact/column';
|
import { Column } from 'primereact/column';
|
||||||
|
|
||||||
@@ -18,9 +18,8 @@ export const Overview: React.FC = () => {
|
|||||||
const regenerateKey = async () => {
|
const regenerateKey = async () => {
|
||||||
Auth.refreshRadarScopeKey().then((status) => {
|
Auth.refreshRadarScopeKey().then((status) => {
|
||||||
if (status === BackendReturnStatus.Unauthorized) {
|
if (status === BackendReturnStatus.Unauthorized) {
|
||||||
|
Session.setLastShownComponent('/overview');
|
||||||
Auth.triggerLoginFlow();
|
Auth.triggerLoginFlow();
|
||||||
// TODO reload user data in auth
|
|
||||||
// TODO store current URL and get back after login
|
|
||||||
} else if (status === BackendReturnStatus.Failure) {
|
} else if (status === BackendReturnStatus.Failure) {
|
||||||
setStatusMessage({
|
setStatusMessage({
|
||||||
error: true,
|
error: true,
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import AuthContext, { AuthProvider } from "./authcontext";
|
import AuthContext, { AuthProvider } from './authcontext';
|
||||||
import TimeContext, { TimeProvider } from "./timecontext";
|
import ThemeContext, { ThemeProvider } from './themecontext';
|
||||||
|
import TimeContext, { TimeProvider } from './timecontext';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AuthContext,
|
AuthContext,
|
||||||
AuthProvider,
|
AuthProvider,
|
||||||
|
ThemeContext,
|
||||||
|
ThemeProvider,
|
||||||
TimeContext,
|
TimeContext,
|
||||||
TimeProvider
|
TimeProvider
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ export const ThemeProvider = ({ children }: { children: any }) => {
|
|||||||
const refDarkMode = useRef<boolean>();
|
const refDarkMode = useRef<boolean>();
|
||||||
refDarkMode.current = darkMode;
|
refDarkMode.current = darkMode;
|
||||||
|
|
||||||
//useEffect(() => {
|
useEffect(() => {
|
||||||
// Session.setTheme(refDarkMode.current ? 'dark' : 'light');
|
Session.setTheme(refDarkMode.current ? 'dark' : 'light');
|
||||||
// window.location.reload();
|
// window.location.reload();
|
||||||
//}, [darkMode]);
|
}, [darkMode]);
|
||||||
|
|
||||||
//if (darkMode) {
|
//if (darkMode) {
|
||||||
// import('primereact/resources/themes/bootstrap4-dark-blue/theme.css');
|
// import('primereact/resources/themes/bootstrap4-dark-blue/theme.css');
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
import React, { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react';
|
||||||
import { Auth, System } from '../services';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { Auth, Session, System } from '../services';
|
||||||
import { BackendReturnStatus } from '../types';
|
import { BackendReturnStatus } from '../types';
|
||||||
|
|
||||||
const TimeContext = React.createContext<{
|
const TimeContext = React.createContext<{
|
||||||
@@ -9,14 +10,19 @@ const TimeContext = React.createContext<{
|
|||||||
|
|
||||||
export const TimeProvider = ({ children }: { children: any }) => {
|
export const TimeProvider = ({ children }: { children: any }) => {
|
||||||
const [offset, setOffset] = useState<number>(0);
|
const [offset, setOffset] = useState<number>(0);
|
||||||
|
const location = useLocation();
|
||||||
|
const refLocation = useRef<string>();
|
||||||
|
|
||||||
|
refLocation.current = location.pathname;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const estimateTimeOffset = async () => {
|
const estimateTimeOffset = async () => {
|
||||||
System.timestamp().then((response) => {
|
System.timestamp().then((response) => {
|
||||||
if (response.status === BackendReturnStatus.Unauthorized) {
|
if (response.status === BackendReturnStatus.Unauthorized) {
|
||||||
|
if (refLocation.current) {
|
||||||
|
Session.setLastShownComponent(refLocation.current);
|
||||||
|
}
|
||||||
Auth.triggerLoginFlow();
|
Auth.triggerLoginFlow();
|
||||||
// TODO reload user data in auth
|
|
||||||
// TODO store current URL and get back after login
|
|
||||||
} else if (response.status === BackendReturnStatus.Ok) {
|
} else if (response.status === BackendReturnStatus.Ok) {
|
||||||
// calculate the time offset (not accurate) between the server and the client to show "correct" times
|
// calculate the time offset (not accurate) between the server and the client to show "correct" times
|
||||||
const clientTimeUtc = new Date().getTime()
|
const clientTimeUtc = new Date().getTime()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export class Session {
|
export class Session {
|
||||||
static reset(): void {
|
static resetBearerToken(): void {
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem('token');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,4 +18,16 @@ export class Session {
|
|||||||
static theme(): string | null {
|
static theme(): string | null {
|
||||||
return localStorage.getItem('theme');
|
return localStorage.getItem('theme');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static resetLastShownComponent(): void {
|
||||||
|
localStorage.removeItem('path');
|
||||||
|
}
|
||||||
|
|
||||||
|
static setLastShownComponent(path: string): void {
|
||||||
|
localStorage.setItem('path', path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static lastShownComponent(): string | null {
|
||||||
|
return localStorage.getItem('path');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user