add UI configuration for sign-in options

This commit is contained in:
Sidney Alcantara
2021-07-01 20:48:41 +10:00
parent 64886b18d7
commit 02f6ed7cfa
10 changed files with 4618 additions and 3470 deletions

View File

@@ -8,7 +8,7 @@
},
"private": true,
"dependencies": {
"@antlerengineering/form-builder": "^0.11.10",
"@antlerengineering/form-builder": "^2.6.0",
"@antlerengineering/multiselect": "^0.9.2",
"@date-io/date-fns": "1.x",
"@material-ui/core": "^4.11.3",
@@ -33,14 +33,14 @@
"lodash": "^4.17.21",
"moment": "^2.29.1",
"query-string": "^6.8.3",
"react": "^16.9.0",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.0.0",
"react-color": "^2.17.3",
"react-data-grid": "7.0.0-canary.30",
"react-div-100vh": "^0.3.8",
"react-dnd": "^11.1.3",
"react-dnd-html5-backend": "^11.1.3",
"react-dom": "^16.9.0",
"react-dom": "^17.0.2",
"react-dropzone": "^10.1.8",
"react-firebaseui": "^5.0.2",
"react-hook-form": "^6.15.5",
@@ -48,12 +48,12 @@
"react-joyride": "^2.3.0",
"react-json-view": "^1.19.1",
"react-router-dom": "^5.0.1",
"react-scripts": "^3.4.3",
"react-scripts": "^4.0.3",
"react-scroll-sync": "^0.8.0",
"react-usestateref": "^1.0.5",
"serve": "^11.3.2",
"tinymce": "^5.2.0",
"typescript": "^3.7.2",
"typescript": "^4.3.5",
"use-algolia": "^1.4.1",
"use-debounce": "^3.3.0",
"use-persisted-state": "^0.3.3",
@@ -94,11 +94,11 @@
"@types/file-saver": "^2.0.1",
"@types/lodash": "^4.14.168",
"@types/node": "^14.14.6",
"@types/react": "^16.9.2",
"@types/react": "^17.0.11",
"@types/react-beautiful-dnd": "^13.0.0",
"@types/react-color": "^3.0.1",
"@types/react-div-100vh": "^0.3.0",
"@types/react-dom": "16.9.0",
"@types/react-dom": "^17.0.8",
"@types/react-router-dom": "^5.1.7",
"@types/use-persisted-state": "^0.3.0",
"firebase-tools": "^8.12.1",

View File

@@ -105,11 +105,9 @@ export const themeBase = {
lineHeight: 1,
},
caption: {
fontFamily: HEADING_FONT,
fontSize: toRem(13),
fontWeight: "bold",
letterSpacing: toEm(0.4, 13),
lineHeight: 16 / 13,
fontSize: toRem(14),
letterSpacing: toEm(0.4, 14),
lineHeight: 20 / 14,
},
overline: {
fontFamily: HEADING_FONT,

View File

@@ -24,6 +24,13 @@ const useStyles = makeStyles((theme) =>
color: theme.palette.text.secondary,
fontFamily: theme.typography.fontFamily,
},
"& .firebaseui-tos": {
...theme.typography.caption,
color: theme.palette.text.disabled,
},
"& .firebaseui-country-selector": {
color: theme.palette.text.primary,
},
"& .firebaseui-title": {
...theme.typography.h5,
color: theme.palette.text.primary,
@@ -87,8 +94,6 @@ const useStyles = makeStyles((theme) =>
},
"& .firebaseui-idp-google": {
backgroundColor: "#4285F4 !important",
"& .firebaseui-idp-icon-wrapper::before": {
content: "''",
display: "block",
@@ -111,9 +116,6 @@ const useStyles = makeStyles((theme) =>
color: "#fff",
},
},
'& .firebaseui-idp-github, & [data-provider-id="microsoft.com"]': {
backgroundColor: "#000 !important",
},
"& .firebaseui-card-header": { padding: 0 },
"& .firebaseui-card-actions": { padding: 0 },
@@ -185,13 +187,8 @@ export default function FirebaseUi(props: Partial<FirebaseUiProps>) {
useEffect(() => {
db.doc("/_FIRETABLE_/publicSettings")
.get()
.then((doc) => {
const signInOptions = doc?.get("signInOptions");
setSignInOptions(signInOptions);
})
.catch(() => {
setSignInOptions({ google: true });
});
.then((doc) => setSignInOptions(doc?.get("signInOptions")))
.catch(() => setSignInOptions(["google"]));
}, []);
if (!signInOptions)
@@ -202,7 +199,6 @@ export default function FirebaseUi(props: Partial<FirebaseUiProps>) {
style={{ marginBottom: 0 }}
>
<Skeleton variant="rect" />
<Skeleton variant="rect" />
</div>
);

View File

@@ -1,14 +1,55 @@
import React from "react";
import { FieldType } from "@antlerengineering/form-builder";
import _startCase from "lodash/startCase";
import { authOptions } from "firebase/firebaseui";
import * as yup from "yup";
import { FIELDS } from "@antlerengineering/form-builder";
import HelperText from "../HelperText";
import { Link } from "@material-ui/core";
import OpenInNewIcon from "@material-ui/icons/OpenInNew";
export const settings = () => [
{ type: FIELDS.heading, label: "Cloud Run configuration" },
export const projectSettingsForm = [
{
type: FIELDS.text,
name: "ftBuildUrl",
label: "Cloud Run trigger URL",
type: FieldType.contentHeader,
name: "_contentHeading_signInOptions",
label: "Authentication",
},
];
{
type: FieldType.multiSelect,
name: "signInOptions",
label: "Sign-In Options",
options: Object.keys(authOptions).map((option) => ({
value: option,
label: _startCase(option).replace("Github", "GitHub"),
})),
defaultValue: ["google"],
required: true,
assistiveText: (
<>
Before enabling a new sign-in option, make sure its configured in your
Firebase project.
<br />
<Link
href={`https://github.com/firebase/firebaseui-web#configuring-sign-in-providers`}
target="_blank"
rel="noopener"
>
How to configure sign-in options
<OpenInNewIcon
aria-label="Open in new tab"
fontSize="small"
style={{ verticalAlign: "bottom", marginLeft: 4 }}
/>
</Link>
</>
) as any,
},
{
type: FieldType.contentHeader,
name: "_contentHeading_cloudRun",
label: "Cloud Run Configuration",
},
{
type: FieldType.shortText,
name: "ftBuildUrl",
label: "Cloud Run Trigger URL",
format: "url",
},
];

View File

@@ -1,75 +1,46 @@
import React, { useState, useEffect } from "react";
import { useState, useEffect } from "react";
import _camelCase from "lodash/camelCase";
import _find from "lodash/find";
import { makeStyles, createStyles } from "@material-ui/core";
import _pickBy from "lodash/pickBy";
import { FormDialog } from "@antlerengineering/form-builder";
import { settings } from "./form";
import { projectSettingsForm } from "./form";
import useDoc, { DocActions } from "hooks/useDoc";
import { IFormDialogProps } from "components/Table/ColumnMenu/NewColumn";
const FORM_EMPTY_STATE = {
cloudBuild: {
branch: "test",
triggerId: "",
},
};
export interface IProjectSettings
extends Pick<IFormDialogProps, "handleClose"> {}
const useStyles = makeStyles((theme) =>
createStyles({
buttonGrid: { padding: theme.spacing(3, 0) },
button: { width: 160 },
formFooter: {
marginTop: theme.spacing(4),
"& button": {
paddingLeft: theme.spacing(1.5),
display: "flex",
},
},
collectionName: { fontFamily: theme.typography.fontFamilyMono },
})
);
export default function SettingsDialog({
open,
handleClose,
}: {
open: boolean;
handleClose: () => void;
}) {
const [settingsDocState, settingsDocDispatch] = useDoc({
export default function ProjectSettings({ handleClose }: IProjectSettings) {
const [settingsState, settingsDispatch] = useDoc({
path: "_FIRETABLE_/settings",
});
const [publicSettingsState, publicSettingsDispatch] = useDoc({
path: "_FIRETABLE_/publicSettings",
});
const [formState, setForm] = useState<any>();
if (settingsState.loading || publicSettingsState.loading) return null;
useEffect(() => {
if (!settingsDocState.loading) {
const ftBuildUrl = settingsDocState?.doc?.ftBuildUrl;
setForm({ ftBuildUrl });
}
}, [settingsDocState.doc, open]);
const handleSubmit = (v) => {
const { signInOptions, ...values } = v;
const handleSubmit = (values) => {
setForm(values)
settingsDocDispatch({ action: DocActions.update, data: values });
handleClose();
settingsDispatch({ action: DocActions.update, data: values });
publicSettingsDispatch({
action: DocActions.update,
data: { signInOptions },
});
};
if (!formState) return <></>;
return (
<>
<FormDialog
onClose={handleClose}
open={open}
title={"Project Settings"}
fields={settings()}
values={formState}
onSubmit={handleSubmit}
/>
</>
<FormDialog
onClose={handleClose}
open
title="Project Settings"
fields={projectSettingsForm}
values={{ ...settingsState.doc, ...publicSettingsState.doc }}
onSubmit={handleSubmit}
SubmitButtonProps={{ children: "Save" }}
/>
);
}

View File

@@ -1,36 +1,30 @@
import React from "react";
import * as yup from "yup";
import { FIELDS } from "@antlerengineering/form-builder";
import { Field, FieldType } from "@antlerengineering/form-builder";
import { TableSettingsDialogModes } from "./index";
import HelperText from "../HelperText";
import { Link, Typography } from "@material-ui/core";
import OpenInNewIcon from "@material-ui/icons/OpenInNew";
import { MONO_FONT } from "Themes";
import {projectId} from '../../firebase'
import { projectId } from "../../firebase";
export const tableSettings = (
mode: TableSettingsDialogModes | null,
roles: string[] | undefined,
sections: string[] | undefined,
tables: { label: string; value: any }[] | undefined
) => [
{
type: FIELDS.text,
name: "name",
label: "Table Name*",
validation: yup.string().required("Required"),
},
{
type: FIELDS.text,
name: "collection",
label: "Collection Name*",
validation: yup.string().required("Required"),
},
{
type: FIELDS.description,
description: (
<HelperText>
): Field[] =>
[
{
type: FieldType.shortText,
name: "name",
label: "Table Name",
required: true,
},
{
type: FieldType.shortText,
name: "collection",
label: "Collection Name",
required: true,
assistiveText: (
<Link
href={`https://console.firebase.google.com/project/${projectId}/firestore/data`}
target="_blank"
@@ -43,113 +37,115 @@ export const tableSettings = (
style={{ verticalAlign: "bottom", marginLeft: 4 }}
/>
</Link>
</HelperText>
),
},
{
type: FIELDS.singleSelect,
name: "tableType",
label: "Table Type*",
defaultValue: "primaryCollection",
options: [
{ label: "Primary Collection", value: "primaryCollection" },
{ label: "Collection Group", value: "collectionGroup" },
],
validation: yup.string().required("Required"),
disabled: mode === TableSettingsDialogModes.update,
},
(values) => ({
type: FIELDS.description,
description:
values.tableType === "primaryCollection" ? (
<HelperText>
Connect this table to the collection named
<span style={{ fontFamily: MONO_FONT }}>{values.collection}</span>
</HelperText>
) : (
<HelperText>
Connect this table to all collections and subcollections named
<span style={{ fontFamily: MONO_FONT }}>{values.collection}</span>
<Link
href="https://firebase.googleblog.com/2019/06/understanding-collection-group-queries.html"
target="_blank"
rel="noopener"
) as any,
},
{
type: FieldType.singleSelect,
name: "tableType",
label: "Table Type",
labelPlural: "table types",
searchable: false,
defaultValue: "primaryCollection",
options: [
{
label: "Primary Collection",
description: `
Connect this table to the <strong>single collection</strong>
matching the collection name entered above`,
value: "primaryCollection",
},
{
label: "Collection Group",
description: `
Connect this table to <strong>all collections and subcollections</strong>
matching the collection name entered above`,
value: "collectionGroup",
},
],
required: true,
disabled: mode === TableSettingsDialogModes.update,
itemRenderer: (option) => (
<span key={option.value}>
{option.label}
<Typography
variant="body2"
color="textSecondary"
component="span"
display="block"
>
Learn more about collection groups
<OpenInNewIcon
aria-label="Open in new tab"
fontSize="small"
style={{ verticalAlign: "bottom", marginLeft: 4 }}
/>
</Link>
</HelperText>
dangerouslySetInnerHTML={{ __html: option.description }}
/>
</span>
),
}),
{
type: FIELDS.multiSelect,
name: "section",
multiple: false,
label: "Section*",
freeText: true,
options: sections,
validation: yup.string().required("Required"),
},
{
type: FIELDS.text,
name: "description",
label: "Description",
fieldVariant: "long",
validation: yup.string(),
},
{
type: FIELDS.multiSelect,
name: "roles",
label: "Accessed By*",
options: roles ?? [],
validation: yup.array().of(yup.string()).required("Required"),
freeText: true,
},
(values) => ({
type: FIELDS.description,
description: (
<HelperText>
Choose which roles have access to this table. Remember to set the
appropriate Firestore Security Rules for the
<span style={{ fontFamily: MONO_FONT }}>{values.collection}</span>
collection.
assistiveText: (
<Link
href="https://github.com/AntlerVC/firetable/wiki/Role-Based-Security-Rules"
href="https://firebase.googleblog.com/2019/06/understanding-collection-group-queries.html"
target="_blank"
rel="noopener"
display="block"
>
Read about role-based security rules
Learn more about collection groups
<OpenInNewIcon
aria-label="Open in new tab"
fontSize="small"
style={{ verticalAlign: "bottom", marginLeft: 4 }}
/>
</Link>
</HelperText>
),
}),
(values) =>
values.tableType === "collectionGroup"
? {
type: FIELDS.slider,
name: "triggerDepth",
defaultValue: 1,
min: 1,
max: 5,
label: `Trigger Depth (used for table cloudFunction trigger Path)`,
hint: "triggerDepth",
}
: null,
() =>
) as any,
},
{
type: FieldType.multiSelect,
name: "section",
multiple: false,
label: "Section",
freeText: true,
options: sections,
required: true,
},
{
type: FieldType.paragraph,
name: "description",
label: "Description",
},
{
type: FieldType.multiSelect,
name: "roles",
label: "Accessed By",
options: roles ?? [],
required: true,
freeText: true,
assistiveText: (
<>
Choose which roles have access to this table. Remember to set the
appropriate Firestore Security Rules for this collection.
<Link
href="https://github.com/AntlerVC/firetable/wiki/Role-Based-Security-Rules"
target="_blank"
rel="noopener"
display="block"
>
Read about role-based security rules
<OpenInNewIcon
aria-label="Open in new tab"
fontSize="small"
style={{ verticalAlign: "bottom", marginLeft: 4 }}
/>
</Link>
</>
) as any,
},
{
type: FieldType.slider,
name: "triggerDepth",
defaultValue: 1,
min: 1,
max: 5,
label: "Cloud Function Trigger Depth",
displayCondition: "return values.tableType === 'collectionGroup'",
assistiveText: "Used for table Cloud Function trigger path",
},
mode === TableSettingsDialogModes.create && tables && tables?.length !== 0
? {
type: FIELDS.multiSelect,
type: FieldType.multiSelect,
name: "schemaSource",
label: "Copy column config from existing table",
labelPlural: "Tables",
@@ -171,4 +167,4 @@ export const tableSettings = (
),
}
: null,
];
].filter((field) => field !== null) as Field[];

View File

@@ -1,6 +1,33 @@
import firebase from "firebase/app";
import * as firebaseui from "firebaseui";
export const authOptions = {
google: {
provider: firebase.auth.GoogleAuthProvider.PROVIDER_ID,
buttonColor: "#4285F4",
},
twitter: firebase.auth.TwitterAuthProvider.PROVIDER_ID,
facebook: firebase.auth.FacebookAuthProvider.PROVIDER_ID,
github: {
provider: firebase.auth.GithubAuthProvider.PROVIDER_ID,
buttonColor: "#000",
},
microsoft: {
provider: "microsoft.com",
loginHintKey: "login_hint",
buttonColor: "#000",
},
apple: { provider: "apple.com" },
yahoo: { provider: "yahoo.com" },
email: {
provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
requireDisplayName: true,
disableSignUp: { status: true },
},
phone: firebase.auth.PhoneAuthProvider.PROVIDER_ID,
anonymous: firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID,
};
export const defaultUiConfig: firebaseui.auth.Config = {
signInFlow: "popup",
signInSuccessUrl: "/",
@@ -10,30 +37,10 @@ export const defaultUiConfig: firebaseui.auth.Config = {
if (node) node.style.display = "none";
},
},
signInOptions: [firebase.auth.GoogleAuthProvider.PROVIDER_ID],
};
const authOptions = {
google: firebase.auth.GoogleAuthProvider.PROVIDER_ID,
twitter: firebase.auth.TwitterAuthProvider.PROVIDER_ID,
facebook: firebase.auth.FacebookAuthProvider.PROVIDER_ID,
github: firebase.auth.GithubAuthProvider.PROVIDER_ID,
microsoft: {
provider: "microsoft.com",
loginHintKey: "login_hint",
},
apple: { provider: "apple.com" },
email: {
provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
requireDisplayName: true,
disableSignUp: { status: true },
},
anonymous: firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID,
signInOptions: [authOptions.google],
};
export const getSignInOptions = (
selected: Partial<Record<keyof typeof authOptions, boolean>>
selected: Array<keyof typeof authOptions>
): firebaseui.auth.Config["signInOptions"] =>
Object.keys(selected)
.map((option) => (selected[option] ? authOptions[option] : null))
.filter((option) => option !== null);
selected.map((option) => authOptions[option]);

View File

@@ -315,10 +315,9 @@ export default function HomePage() {
mode={settingsDialogState.mode}
data={settingsDialogState.data}
/>
<ProjectSettings
open={openProjectSettings}
handleClose={() => setOpenProjectSettings(false)}
/>
{openProjectSettings && (
<ProjectSettings handleClose={() => setOpenProjectSettings(false)} />
)}
</HomeNavigation>
);
}

View File

@@ -14,8 +14,9 @@
"isolatedModules": true,
"noEmit": true,
"noImplicitAny": false,
"jsx": "react",
"baseUrl": "src"
"jsx": "react-jsx",
"baseUrl": "src",
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}

File diff suppressed because it is too large Load Diff