QQQ-32: added booleans, cleaned up error handling, fixed infinite loop on unauthorized login, removed all the login buttons, removed redundant qClient functions

This commit is contained in:
Tim Chamberlain
2022-08-09 11:52:32 -05:00
parent c4b72c4b11
commit 7cb3c5ee88
12 changed files with 81 additions and 104 deletions

View File

@ -13,7 +13,7 @@
"@fullcalendar/interaction": "5.10.0", "@fullcalendar/interaction": "5.10.0",
"@fullcalendar/react": "5.10.0", "@fullcalendar/react": "5.10.0",
"@fullcalendar/timegrid": "5.10.0", "@fullcalendar/timegrid": "5.10.0",
"@kingsrook/qqq-frontend-core": "1.0.6", "@kingsrook/qqq-frontend-core": "1.0.7",
"@mui/icons-material": "5.4.1", "@mui/icons-material": "5.4.1",
"@mui/material": "5.4.1", "@mui/material": "5.4.1",
"@mui/styled-engine": "5.4.1", "@mui/styled-engine": "5.4.1",

View File

@ -38,9 +38,7 @@ import {useMaterialUIController, setMiniSidenav, setOpenConfigurator} from "cont
// Images // Images
import nfLogo from "assets/images/nutrifresh_one_icon_white.png"; import nfLogo from "assets/images/nutrifresh_one_icon_white.png";
import {Md5} from "ts-md5/dist/md5"; import {Md5} from "ts-md5/dist/md5";
import AuthenticationButton from "qqq/components/buttons/AuthenticationButton";
import {useCookies} from "react-cookie"; import {useCookies} from "react-cookie";
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import EntityCreate from "./qqq/pages/entity-create"; import EntityCreate from "./qqq/pages/entity-create";
import EntityList from "./qqq/pages/entity-list"; import EntityList from "./qqq/pages/entity-list";
import EntityView from "./qqq/pages/entity-view"; import EntityView from "./qqq/pages/entity-view";
@ -49,7 +47,6 @@ import ProcessRun from "./qqq/pages/process-run";
import MDAvatar from "./components/MDAvatar"; import MDAvatar from "./components/MDAvatar";
import ProfileOverview from "./layouts/pages/profile/profile-overview"; import ProfileOverview from "./layouts/pages/profile/profile-overview";
import Settings from "./layouts/pages/account/settings"; import Settings from "./layouts/pages/account/settings";
import SignInBasic from "./layouts/authentication/sign-in/basic";
import Analytics from "./layouts/dashboards/analytics"; import Analytics from "./layouts/dashboards/analytics";
import Sales from "./layouts/dashboards/sales"; import Sales from "./layouts/dashboards/sales";
import QClient from "./qqq/utils/QClient"; import QClient from "./qqq/utils/QClient";
@ -86,14 +83,14 @@ function getStaticRoutes()
]; ];
} }
const SESSION_ID_COOKIE_NAME = "sessionId"; export const SESSION_ID_COOKIE_NAME = "sessionId";
LicenseInfo.setLicenseKey(process.env.REACT_APP_MATERIAL_UI_LICENSE_KEY); LicenseInfo.setLicenseKey(process.env.REACT_APP_MATERIAL_UI_LICENSE_KEY);
export default function App() export default function App()
{ {
const [, setCookie] = useCookies([SESSION_ID_COOKIE_NAME]); const [, setCookie] = useCookies([SESSION_ID_COOKIE_NAME]);
const { const {
user, getAccessTokenSilently, getIdTokenClaims, logout, user, getAccessTokenSilently, getIdTokenClaims, logout, loginWithRedirect,
} = useAuth0(); } = useAuth0();
const [loadingToken, setLoadingToken] = useState(false); const [loadingToken, setLoadingToken] = useState(false);
const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false); const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false);
@ -110,7 +107,7 @@ export default function App()
try try
{ {
console.log("Loading token..."); console.log("Loading token...");
const accessToken = await getAccessTokenSilently(); await getAccessTokenSilently();
const idToken = await getIdTokenClaims(); const idToken = await getIdTokenClaims();
setCookie(SESSION_ID_COOKIE_NAME, idToken.__raw, {path: "/"}); setCookie(SESSION_ID_COOKIE_NAME, idToken.__raw, {path: "/"});
setIsFullyAuthenticated(true); setIsFullyAuthenticated(true);
@ -131,8 +128,6 @@ export default function App()
layout, layout,
openConfigurator, openConfigurator,
sidenavColor, sidenavColor,
transparentSidenav,
whiteSidenav,
darkMode, darkMode,
} = controller; } = controller;
const [onMouseEnter, setOnMouseEnter] = useState(false); const [onMouseEnter, setOnMouseEnter] = useState(false);
@ -156,8 +151,7 @@ export default function App()
{ {
try try
{ {
console.log("ok now loading qqq things"); const metaData = await QClient.getInstance().loadMetaData();
const metaData = await QClient.loadMetaData();
// get the keys sorted // get the keys sorted
const keys = [...metaData.tables.keys()].sort((a, b): number => const keys = [...metaData.tables.keys()].sort((a, b): number =>
@ -203,12 +197,6 @@ export default function App()
route: "/pages/account/settings", route: "/pages/account/settings",
component: <Settings />, component: <Settings />,
}, },
{
name: "Logout",
key: "logout",
route: "/authentication/sign-in/basic",
component: <SignInBasic />,
},
], ],
}; };
@ -294,27 +282,6 @@ export default function App()
}, },
); );
const authButton = (
<MDBox
display="flex"
justifyContent="center"
alignItems="center"
width="3.25rem"
height="3.25rem"
bgColor="white"
shadow="sm"
borderRadius="50%"
position="fixed"
right="2rem"
bottom="2rem"
zIndex={99}
color="dark"
sx={{cursor: "pointer"}}
>
<AuthenticationButton />
</MDBox>
);
const configsButton = ( const configsButton = (
<MDBox <MDBox
display="flex" display="flex"
@ -360,7 +327,6 @@ export default function App()
/> />
<Configurator /> <Configurator />
{configsButton} {configsButton}
{authButton}
</> </>
)} )}
<Routes> <Routes>

View File

@ -0,0 +1,29 @@
import React, {
useState,
useEffect,
JSXElementConstructor,
Key,
ReactElement,
} from "react";
import {SESSION_ID_COOKIE_NAME} from "App";
import {useCookies} from "react-cookie";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
interface Props
{
errorMessage?: string;
}
export default function HandleAuthorizationError({errorMessage}: Props)
{
const [, , removeCookie] = useCookies([SESSION_ID_COOKIE_NAME]);
useEffect(() =>
{
removeCookie(SESSION_ID_COOKIE_NAME, {path: "/"});
});
return (
<div>{errorMessage}</div>
);
}

View File

@ -44,6 +44,7 @@ import {
setTransparentSidenav, setTransparentSidenav,
setWhiteSidenav, setWhiteSidenav,
} from "context"; } from "context";
import AuthenticationButton from "qqq/components/Buttons/AuthenticationButton";
// Declaring props types for Sidenav // Declaring props types for Sidenav
interface Props { interface Props {
@ -314,6 +315,7 @@ function Sidenav({ color, brand, brandName, routes, ...rest }: Props): JSX.Eleme
} }
/> />
<List>{renderRoutes}</List> <List>{renderRoutes}</List>
<AuthenticationButton />
</SidenavRoot> </SidenavRoot>
); );
} }

View File

@ -1,5 +1,5 @@
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import {BrowserRouter, useNavigate} from "react-router-dom"; import {BrowserRouter, useNavigate, useSearchParams} from "react-router-dom";
import {Auth0Provider} from "@auth0/auth0-react"; import {Auth0Provider} from "@auth0/auth0-react";
import App from "App"; import App from "App";
@ -8,39 +8,39 @@ import {MaterialUIControllerProvider} from "context";
import "./qqq/styles/qqq-override-styles.css"; import "./qqq/styles/qqq-override-styles.css";
import ProtectedRoute from "qqq/auth0/protected-route"; import ProtectedRoute from "qqq/auth0/protected-route";
import React from "react"; import React from "react";
import HandleAuthorizationError from "HandleAuthorizationError";
// Auth0 params from env // Auth0 params from env
const domain = process.env.REACT_APP_AUTH0_DOMAIN; const domain = process.env.REACT_APP_AUTH0_DOMAIN;
const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID; const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID;
/*
ReactDOM.render(
<BrowserRouter>
<MaterialUIControllerProvider>
<ProtectedRoute component={App} />
</MaterialUIControllerProvider>
</BrowserRouter>,
document.getElementById("root"),
);
*/
console.log("what");
// @ts-ignore // @ts-ignore
function Auth0ProviderWithRedirectCallback({children, ...props}) function Auth0ProviderWithRedirectCallback({children, ...props})
{ {
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams] = useSearchParams();
// @ts-ignore // @ts-ignore
const onRedirectCallback = (appState) => const onRedirectCallback = (appState) =>
{ {
navigate((appState && appState.returnTo) || window.location.pathname); navigate((appState && appState.returnTo) || window.location.pathname);
}; };
return ( if (searchParams.get("error"))
// @ts-ignore {
<Auth0Provider onRedirectCallback={onRedirectCallback} {...props}> return (
{children} // @ts-ignore
</Auth0Provider> <HandleAuthorizationError errorMessage={searchParams.get("error_description")} />
); );
}
else
{
return (
// @ts-ignore
<Auth0Provider onRedirectCallback={onRedirectCallback} {...props}>
{children}
</Auth0Provider>
);
}
} }
ReactDOM.render( ReactDOM.render(
@ -57,5 +57,3 @@ ReactDOM.render(
</BrowserRouter>, </BrowserRouter>,
document.getElementById("root"), document.getElementById("root"),
); );
export * from "components/MDButton";

View File

@ -21,6 +21,7 @@ import {Alert} from "@mui/material";
import MDBox from "components/MDBox"; import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography"; import MDTypography from "components/MDTypography";
import MDButton from "../../../components/MDButton"; import MDButton from "../../../components/MDButton";
import QClient from "qqq/utils/QClient";
// Declaring props types for EntityForm // Declaring props types for EntityForm
interface Props interface Props
@ -30,7 +31,7 @@ interface Props
function EntityForm({id}: Props): JSX.Element function EntityForm({id}: Props): JSX.Element
{ {
const qController = new QController(""); const qController = QClient.getInstance();
const {tableName} = useParams(); const {tableName} = useParams();
const [validations, setValidations] = useState({}); const [validations, setValidations] = useState({});

View File

@ -38,7 +38,6 @@ import {
} from "context"; } from "context";
// qqq // qqq
import AuthenticationButton from "qqq/components/buttons/AuthenticationButton";
// Declaring prop types for Navbar // Declaring prop types for Navbar
interface Props interface Props
@ -159,14 +158,6 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element
<MDInput label="Search here" /> <MDInput label="Search here" />
</MDBox> </MDBox>
<MDBox color={light ? "white" : "inherit"}> <MDBox color={light ? "white" : "inherit"}>
<AuthenticationButton />
{ /*
<Link to="/authentication/sign-in/basic">
<IconButton sx={navbarIconButton} size="small" disableRipple>
<Icon sx={iconsStyle}>account_circle</Icon>
</IconButton>
</Link>
*/ }
<IconButton <IconButton
size="small" size="small"
disableRipple disableRipple

View File

@ -66,6 +66,9 @@ class DynamicFormUtils
case QFieldType.BLOB: case QFieldType.BLOB:
fieldType = "file"; fieldType = "file";
break; break;
case QFieldType.BOOLEAN:
fieldType = "checkbox";
break;
case QFieldType.TEXT: case QFieldType.TEXT:
case QFieldType.HTML: case QFieldType.HTML:
case QFieldType.STRING: case QFieldType.STRING:

View File

@ -87,6 +87,7 @@ function EntityList({table}: Props): JSX.Element
const columnVisibilityLocalStorageKey = `${COLUMN_VISIBILITY_LOCAL_STORAGE_KEY_ROOT}.${tableName}`; const columnVisibilityLocalStorageKey = `${COLUMN_VISIBILITY_LOCAL_STORAGE_KEY_ROOT}.${tableName}`;
let defaultSort = [] as GridSortItem[]; let defaultSort = [] as GridSortItem[];
let defaultVisibility = {}; let defaultVisibility = {};
const qController = QClient.getInstance();
if (localStorage.getItem(sortLocalStorageKey)) if (localStorage.getItem(sortLocalStorageKey))
{ {
@ -193,7 +194,7 @@ function EntityList({table}: Props): JSX.Element
{ {
(async () => (async () =>
{ {
const newTableMetaData = await QClient.loadTableMetaData(tableName); const newTableMetaData = await qController.loadTableMetaData(tableName);
setTableMetaData(newTableMetaData); setTableMetaData(newTableMetaData);
if (columnSortModel.length === 0) if (columnSortModel.length === 0)
{ {
@ -206,14 +207,14 @@ function EntityList({table}: Props): JSX.Element
const qFilter = buildQFilter(); const qFilter = buildQFilter();
const count = await QClient.count(tableName, qFilter); const count = await qController.count(tableName, qFilter);
setTotalRecords(count); setTotalRecords(count);
setButtonText(`new ${newTableMetaData.label}`); setButtonText(`new ${newTableMetaData.label}`);
setTableLabel(newTableMetaData.label); setTableLabel(newTableMetaData.label);
const columns = [] as GridColDef[]; const columns = [] as GridColDef[];
const results = await QClient.query( const results = await qController.query(
tableName, tableName,
qFilter, qFilter,
rowsPerPage, rowsPerPage,
@ -362,7 +363,7 @@ function EntityList({table}: Props): JSX.Element
setTableState(tableName); setTableState(tableName);
setFilterModel(null); setFilterModel(null);
setFiltersMenu(null); setFiltersMenu(null);
const metaData = await QClient.loadMetaData(); const metaData = await qController.loadMetaData();
setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName)); setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName));

View File

@ -38,8 +38,9 @@ import Icon from "@mui/material/Icon";
import MDAlert from "components/MDAlert"; import MDAlert from "components/MDAlert";
import MDButton from "../../../../../components/MDButton"; import MDButton from "../../../../../components/MDButton";
import QProcessUtils from "../../../../utils/QProcessUtils"; import QProcessUtils from "../../../../utils/QProcessUtils";
import QClient from "qqq/utils/QClient";
const qController = new QController(""); const qController = QClient.getInstance();
// Declaring props types for ViewForm // Declaring props types for ViewForm
interface Props interface Props

View File

@ -19,9 +19,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController"; import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController";
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
import {useAuth0} from "@auth0/auth0-react";
/******************************************************************************* /*******************************************************************************
** client wrapper of qqq backend ** client wrapper of qqq backend
@ -31,40 +31,25 @@ class QClient
{ {
private static qController: QController; private static qController: QController;
private static handleException(exception: QException)
{
console.log(`Caught Exception: ${JSON.stringify(exception)}`);
const {logout} = useAuth0();
if (exception.status === "401")
{
logout();
}
}
public static getInstance() public static getInstance()
{ {
if (this.qController == null) if (this.qController == null)
{ {
this.qController = new QController(""); this.qController = new QController("", this.handleException);
} }
return this.qController; return this.qController;
} }
public static loadTableMetaData(tableName: string)
{
return this.getInstance().loadTableMetaData(tableName);
}
public static loadMetaData()
{
return this.getInstance().loadMetaData();
}
public static query(tableName: string, filter: QQueryFilter, limit: number, skip: number)
{
return this.getInstance()
.query(tableName, filter, limit, skip)
.catch((error) =>
{
throw error;
});
}
public static count(tableName: string, filter: QQueryFilter)
{
return this.getInstance().count(tableName, filter);
}
} }
export default QClient; export default QClient;