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