mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 21:00:45 +00:00
Try to use google login separately from picker, to get an access token with more scopes
This commit is contained in:
@ -23,6 +23,7 @@
|
|||||||
"@react-jvectormap/core": "1.0.1",
|
"@react-jvectormap/core": "1.0.1",
|
||||||
"@react-jvectormap/unitedstates": "1.0.1",
|
"@react-jvectormap/unitedstates": "1.0.1",
|
||||||
"@react-jvectormap/world": "1.0.0",
|
"@react-jvectormap/world": "1.0.0",
|
||||||
|
"@react-oauth/google": "0.2.8",
|
||||||
"@testing-library/jest-dom": "5.16.2",
|
"@testing-library/jest-dom": "5.16.2",
|
||||||
"@testing-library/react": "12.1.2",
|
"@testing-library/react": "12.1.2",
|
||||||
"@testing-library/user-event": "13.5.0",
|
"@testing-library/user-event": "13.5.0",
|
||||||
|
@ -23,12 +23,19 @@ import {colors} from "@mui/material"
|
|||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
|
import {useGoogleLogin} from "@react-oauth/google";
|
||||||
import {useFormikContext} from "formik";
|
import {useFormikContext} from "formik";
|
||||||
import React, {useEffect, useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import useDrivePicker from "react-google-drive-picker";
|
import useDrivePicker from "react-google-drive-picker";
|
||||||
import MDBox from "qqq/components/Temporary/MDBox";
|
import MDTypography from "qqq/components/Temporary/MDTypography";
|
||||||
|
|
||||||
export function QGoogleDriveFolderPicker(): JSX.Element
|
interface Props
|
||||||
|
{
|
||||||
|
showDefaultFoldersView: boolean;
|
||||||
|
showSharedDrivesView: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QGoogleDriveFolderPicker({showDefaultFoldersView, showSharedDrivesView}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const clientId = process.env.REACT_APP_GOOGLE_APP_CLIENT_ID;
|
const clientId = process.env.REACT_APP_GOOGLE_APP_CLIENT_ID;
|
||||||
const appApiKey = process.env.REACT_APP_GOOGLE_APP_API_KEY;
|
const appApiKey = process.env.REACT_APP_GOOGLE_APP_API_KEY;
|
||||||
@ -36,24 +43,71 @@ export function QGoogleDriveFolderPicker(): JSX.Element
|
|||||||
const [ selectedGoogleFolderName, setSelectedGoogleFolderName ] = useState(null as string);
|
const [ selectedGoogleFolderName, setSelectedGoogleFolderName ] = useState(null as string);
|
||||||
const [ selectedGoogleFolderId, setSelectedGoogleFolderId ] = useState(null as string);
|
const [ selectedGoogleFolderId, setSelectedGoogleFolderId ] = useState(null as string);
|
||||||
const [ googleToken, setGoogleToken ] = useState(null as string); // maybe get from cookie/local-storage
|
const [ googleToken, setGoogleToken ] = useState(null as string); // maybe get from cookie/local-storage
|
||||||
|
const [ errorMessage, setErrorMessage ] = useState(null as string);
|
||||||
|
|
||||||
const [ openPicker, authResponse ] = useDrivePicker();
|
const [ openPicker, authResponse ] = useDrivePicker();
|
||||||
|
|
||||||
const formikProps = useFormikContext();
|
const formikProps = useFormikContext();
|
||||||
|
|
||||||
|
const loginOrOpenPicker = (token: string) =>
|
||||||
|
{
|
||||||
|
if(token)
|
||||||
|
{
|
||||||
|
handleOpenPicker(token);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
login();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const login = useGoogleLogin({
|
||||||
|
scope: "https://www.googleapis.com/auth/drive",
|
||||||
|
onSuccess: tokenResponse =>
|
||||||
|
{
|
||||||
|
console.log("Token response");
|
||||||
|
console.log(tokenResponse);
|
||||||
|
setGoogleToken(tokenResponse.access_token)
|
||||||
|
handleOpenPicker(tokenResponse.access_token);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const handleOpenPicker = (token: string) =>
|
const handleOpenPicker = (token: string) =>
|
||||||
{
|
{
|
||||||
|
// @ts-ignore
|
||||||
|
const google = window.google
|
||||||
|
const customViewsArray: any[] = [];
|
||||||
|
|
||||||
|
if(showDefaultFoldersView)
|
||||||
|
{
|
||||||
|
customViewsArray.push(new google.picker.DocsView(google.picker.ViewId.FOLDERS)
|
||||||
|
.setIncludeFolders(true)
|
||||||
|
.setMode(google.picker.DocsViewMode.LIST)
|
||||||
|
.setSelectFolderEnabled(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(showSharedDrivesView)
|
||||||
|
{
|
||||||
|
customViewsArray.push(new google.picker.DocsView(google.picker.ViewId.FOLDERS)
|
||||||
|
.setEnableDrives(true)
|
||||||
|
.setIncludeFolders(true)
|
||||||
|
.setMode(google.picker.DocsViewMode.LIST)
|
||||||
|
.setSelectFolderEnabled(true));
|
||||||
|
}
|
||||||
|
|
||||||
openPicker({
|
openPicker({
|
||||||
clientId: clientId,
|
clientId: clientId,
|
||||||
developerKey: appApiKey,
|
developerKey: appApiKey,
|
||||||
viewId: "FOLDERS",
|
viewId: "FOLDERS",
|
||||||
token: token, // pass oauth token in case you already have one
|
token: token, // pass oauth token in case you already have one
|
||||||
|
disableDefaultView: (customViewsArray.length > 0), // if we have any custom views, then disable the default.
|
||||||
showUploadView: false,
|
showUploadView: false,
|
||||||
showUploadFolders: false,
|
showUploadFolders: false,
|
||||||
supportDrives: true,
|
supportDrives: true,
|
||||||
multiselect: false,
|
multiselect: false,
|
||||||
setSelectFolderEnabled: true,
|
setSelectFolderEnabled: true,
|
||||||
setIncludeFolders: true,
|
setIncludeFolders: true,
|
||||||
|
customViews: customViewsArray,
|
||||||
callbackFunction: (data) =>
|
callbackFunction: (data) =>
|
||||||
{
|
{
|
||||||
if (data.action === "cancel")
|
if (data.action === "cancel")
|
||||||
@ -62,49 +116,61 @@ export function QGoogleDriveFolderPicker(): JSX.Element
|
|||||||
setSelectedGoogleFolderId(null);
|
setSelectedGoogleFolderId(null);
|
||||||
setSelectedGoogleFolderName(null);
|
setSelectedGoogleFolderName(null);
|
||||||
}
|
}
|
||||||
else
|
else if (data.action === "picked")
|
||||||
{
|
{
|
||||||
console.log(data);
|
console.log(data);
|
||||||
setSelectedGoogleFolderId(data.docs[0].id);
|
const mimeType = data.docs[0].mimeType;
|
||||||
setSelectedGoogleFolderName(data.docs[0].name);
|
if(mimeType === "application/vnd.google-apps.folder")
|
||||||
|
{
|
||||||
|
setSelectedGoogleFolderId(data.docs[0].id);
|
||||||
|
setSelectedGoogleFolderName(data.docs[0].name);
|
||||||
|
setErrorMessage(null)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setSelectedGoogleFolderId(null);
|
||||||
|
setSelectedGoogleFolderName(null);
|
||||||
|
setErrorMessage("You selected a file, but a folder is required.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.log("Called with un-recognized action:");
|
||||||
|
console.log(data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if(authResponse && authResponse.access_token && authResponse.access_token !== googleToken)
|
|
||||||
{
|
|
||||||
setGoogleToken(authResponse.access_token);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
formikProps.setFieldValue("googleDriveAccessToken", authResponse?.access_token);
|
formikProps.setFieldValue("googleDriveAccessToken", googleToken);
|
||||||
formikProps.setFieldValue("googleDriveFolderId", selectedGoogleFolderId);
|
formikProps.setFieldValue("googleDriveFolderId", selectedGoogleFolderId);
|
||||||
formikProps.setFieldValue("googleDriveFolderName", selectedGoogleFolderName);
|
formikProps.setFieldValue("googleDriveFolderName", selectedGoogleFolderName);
|
||||||
}, [selectedGoogleFolderId, selectedGoogleFolderName])
|
}, [selectedGoogleFolderId, selectedGoogleFolderName])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid item xs={12} sm={6}>
|
<Grid item xs={12} sm={6}>
|
||||||
<MDBox mb={1.5}>
|
<Box mb={1.5}>
|
||||||
|
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
<Button variant="outlined" onClick={() => handleOpenPicker(googleToken)}>
|
<Button variant="outlined" onClick={() => loginOrOpenPicker(googleToken)}>
|
||||||
<span style={{color: colors.lightBlue[500]}}>Select Google Drive Folder</span>
|
<span style={{color: colors.lightBlue[500]}}>Select Google Drive Folder</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Box ml={1} fontSize={"1rem"}>
|
<Box ml={1} fontSize={"1rem"}>
|
||||||
{selectedGoogleFolderName}
|
{selectedGoogleFolderName}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box mt={0.75}>
|
||||||
{/*
|
|
||||||
<MDBox mt={0.75}>
|
|
||||||
<MDTypography component="div" variant="caption" color="error" fontWeight="regular">
|
<MDTypography component="div" variant="caption" color="error" fontWeight="regular">
|
||||||
{errors[fieldName] && <span>You must select a file to proceed</span>}
|
<div className="fieldErrorMessage">{errorMessage}</div>
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
</MDBox>
|
</Box>
|
||||||
*/}
|
</Box>
|
||||||
</MDBox>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QGoogleDriveFolderPicker.defaultProps = {
|
||||||
|
showDefaultFoldersView: true,
|
||||||
|
showSharedDrivesView: true
|
||||||
|
};
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {GoogleOAuthProvider} from "@react-oauth/google";
|
||||||
|
import React from "react";
|
||||||
|
import {QGoogleDriveFolderPicker} from "qqq/pages/process-run/components/QGoogleDriveFolderPicker";
|
||||||
|
|
||||||
|
interface Props
|
||||||
|
{
|
||||||
|
showDefaultFoldersView: boolean;
|
||||||
|
showSharedDrivesView: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QGoogleDriveFolderPickerWrapper({showDefaultFoldersView, showSharedDrivesView}: Props): JSX.Element
|
||||||
|
{
|
||||||
|
const clientId = process.env.REACT_APP_GOOGLE_APP_CLIENT_ID;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GoogleOAuthProvider clientId={clientId}>
|
||||||
|
<QGoogleDriveFolderPicker showDefaultFoldersView={showDefaultFoldersView} showSharedDrivesView={showSharedDrivesView} />
|
||||||
|
</GoogleOAuthProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
QGoogleDriveFolderPickerWrapper.defaultProps = {
|
||||||
|
showDefaultFoldersView: true,
|
||||||
|
showSharedDrivesView: true
|
||||||
|
};
|
@ -41,6 +41,7 @@ import StepLabel from "@mui/material/StepLabel";
|
|||||||
import Stepper from "@mui/material/Stepper";
|
import Stepper from "@mui/material/Stepper";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import {DataGridPro, GridColDef} from "@mui/x-data-grid-pro";
|
import {DataGridPro, GridColDef} from "@mui/x-data-grid-pro";
|
||||||
|
import {GoogleOAuthProvider} from "@react-oauth/google";
|
||||||
import FormData from "form-data";
|
import FormData from "form-data";
|
||||||
import {Form, Formik} from "formik";
|
import {Form, Formik} from "formik";
|
||||||
import React, {useEffect, useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
@ -54,7 +55,7 @@ import MDBox from "qqq/components/Temporary/MDBox";
|
|||||||
import MDButton from "qqq/components/Temporary/MDButton";
|
import MDButton from "qqq/components/Temporary/MDButton";
|
||||||
import MDProgress from "qqq/components/Temporary/MDProgress";
|
import MDProgress from "qqq/components/Temporary/MDProgress";
|
||||||
import MDTypography from "qqq/components/Temporary/MDTypography";
|
import MDTypography from "qqq/components/Temporary/MDTypography";
|
||||||
import {QGoogleDriveFolderPicker} from "qqq/pages/process-run/components/QGoogleDriveFolderPicker";
|
import {QGoogleDriveFolderPickerWrapper} from "qqq/pages/process-run/components/QGoogleDriveFolderPickerWrapper";
|
||||||
import QValidationReview from "qqq/pages/process-run/components/QValidationReview";
|
import QValidationReview from "qqq/pages/process-run/components/QValidationReview";
|
||||||
import QClient from "qqq/utils/QClient";
|
import QClient from "qqq/utils/QClient";
|
||||||
import QValueUtils from "qqq/utils/QValueUtils";
|
import QValueUtils from "qqq/utils/QValueUtils";
|
||||||
@ -181,21 +182,6 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element
|
|||||||
setDisabledBulkEditFields(newDisabledBulkEditFields);
|
setDisabledBulkEditFields(newDisabledBulkEditFields);
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatViewValue = (value: any): JSX.Element =>
|
|
||||||
{
|
|
||||||
if (value === null || value === undefined)
|
|
||||||
{
|
|
||||||
return <span> </span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === "string")
|
|
||||||
{
|
|
||||||
return QValueUtils.breakTextIntoLines(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (<span>{value}</span>);
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleShowErrorDetail = () =>
|
const toggleShowErrorDetail = () =>
|
||||||
{
|
{
|
||||||
setShowErrorDetail(!showErrorDetail);
|
setShowErrorDetail(!showErrorDetail);
|
||||||
@ -335,7 +321,7 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element
|
|||||||
:
|
:
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||||
{formatViewValue(processValues[field.name])}
|
{QValueUtils.getValueForDisplay(field, processValues[field.name])}
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
))}
|
))}
|
||||||
@ -396,7 +382,8 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
component.type === QComponentType.GOOGLE_DRIVE_SELECT_FOLDER && (
|
component.type === QComponentType.GOOGLE_DRIVE_SELECT_FOLDER && (
|
||||||
<QGoogleDriveFolderPicker />
|
// todo - make these booleans configurable (values on the component)
|
||||||
|
<QGoogleDriveFolderPickerWrapper showSharedDrivesView={true} showDefaultFoldersView={false} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
@ -26,7 +26,6 @@ import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
|||||||
import "datejs";
|
import "datejs";
|
||||||
import {Chip, Icon} from "@mui/material";
|
import {Chip, Icon} from "@mui/material";
|
||||||
import React, {Fragment} from "react";
|
import React, {Fragment} from "react";
|
||||||
import {Link} from "react-router-dom";
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Utility class for working with QQQ Values
|
** Utility class for working with QQQ Values
|
||||||
@ -34,15 +33,29 @@ import {Link} from "react-router-dom";
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
class QValueUtils
|
class QValueUtils
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** When you have a field, and a record - call this method to get a string or
|
||||||
|
** element back to display the field's value.
|
||||||
|
*******************************************************************************/
|
||||||
public static getDisplayValue(field: QFieldMetaData, record: QRecord): string | JSX.Element
|
public static getDisplayValue(field: QFieldMetaData, record: QRecord): string | JSX.Element
|
||||||
{
|
{
|
||||||
const displayValue = record.displayValues ? record.displayValues.get(field.name) : undefined;
|
const displayValue = record.displayValues ? record.displayValues.get(field.name) : undefined;
|
||||||
const rawValue = record.values ? record.values.get(field.name) : undefined;
|
const rawValue = record.values ? record.values.get(field.name) : undefined;
|
||||||
|
|
||||||
|
return QValueUtils.getValueForDisplay(field, rawValue, displayValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** When you have a field and a value (either just a raw value, or a raw and
|
||||||
|
** display value), call this method to get a string Element to display.
|
||||||
|
*******************************************************************************/
|
||||||
|
public static getValueForDisplay(field: QFieldMetaData, rawValue: any, displayValue: any = rawValue): string | JSX.Element
|
||||||
|
{
|
||||||
if (field.hasAdornment(AdornmentType.LINK))
|
if (field.hasAdornment(AdornmentType.LINK))
|
||||||
{
|
{
|
||||||
const adornment = field.getAdornment(AdornmentType.LINK);
|
const adornment = field.getAdornment(AdornmentType.LINK);
|
||||||
return (<a target={adornment.getValue("target") ?? "_self"} href={rawValue} onClick={(e) =>
|
return (<a target={adornment.getValue("target") ?? "_self"} href={rawValue} onClick={(e) =>
|
||||||
{
|
{
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}>{rawValue}</a>)
|
}}>{rawValue}</a>)
|
||||||
@ -62,14 +75,15 @@ class QValueUtils
|
|||||||
return (<Chip label={displayValue} color={color} icon={iconElement} size="small" variant="outlined" sx={{fontWeight: 500}} />);
|
return (<Chip label={displayValue} color={color} icon={iconElement} size="small" variant="outlined" sx={{fontWeight: 500}} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (QValueUtils._getDisplayValueString(field, record));
|
return (QValueUtils.getUnadornedValueForDisplay(field, rawValue, displayValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static _getDisplayValueString(field: QFieldMetaData, record: QRecord): string
|
/*******************************************************************************
|
||||||
|
** After we know there's no element to be returned (e.g., because no adornment),
|
||||||
|
** this method does the string formatting.
|
||||||
|
*******************************************************************************/
|
||||||
|
private static getUnadornedValueForDisplay(field: QFieldMetaData, rawValue: any, displayValue: any): string | JSX.Element
|
||||||
{
|
{
|
||||||
const displayValue = record.displayValues ? record.displayValues.get(field.name) : undefined;
|
|
||||||
const rawValue = record.values ? record.values.get(field.name) : undefined;
|
|
||||||
|
|
||||||
if (field.type === QFieldType.DATE_TIME)
|
if (field.type === QFieldType.DATE_TIME)
|
||||||
{
|
{
|
||||||
if (!rawValue)
|
if (!rawValue)
|
||||||
@ -90,12 +104,18 @@ class QValueUtils
|
|||||||
return (displayValue);
|
return (displayValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let returnValue = displayValue;
|
||||||
if (displayValue === undefined && rawValue !== undefined)
|
if (displayValue === undefined && rawValue !== undefined)
|
||||||
{
|
{
|
||||||
return (rawValue);
|
returnValue = rawValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (displayValue);
|
if (typeof returnValue === "string" && returnValue.indexOf("\n") > -1)
|
||||||
|
{
|
||||||
|
return QValueUtils.breakTextIntoLines(returnValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (returnValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getFormattedNumber(n: number): string
|
public static getFormattedNumber(n: number): string
|
||||||
@ -117,7 +137,6 @@ class QValueUtils
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static breakTextIntoLines(value: string): JSX.Element
|
public static breakTextIntoLines(value: string): JSX.Element
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
|
Reference in New Issue
Block a user