From 0dd56f27e39286f6f0b785bf61cc5ae5e9d2c441 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Wed, 9 Feb 2022 19:33:05 +1100 Subject: [PATCH 01/18] update access denied copy --- src/components/Home/AccessDenied.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Home/AccessDenied.tsx b/src/components/Home/AccessDenied.tsx index 5c6b9024..a718cbbd 100644 --- a/src/components/Home/AccessDenied.tsx +++ b/src/components/Home/AccessDenied.tsx @@ -19,7 +19,7 @@ export default function AccessDenied() { description={ <> - You are currently signed in as {currentUser?.email} + You are signed in as {currentUser?.email} You do not have access to this project. Please contact the project From cf10f9ceb14a9a87d4e5cca105911ec039c1c178 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Thu, 10 Feb 2022 16:43:30 +1100 Subject: [PATCH 02/18] remove snackbar when rowyRunUrl is not present --- src/contexts/ProjectContext.tsx | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/contexts/ProjectContext.tsx b/src/contexts/ProjectContext.tsx index 1729a3e6..a37cd671 100644 --- a/src/contexts/ProjectContext.tsx +++ b/src/contexts/ProjectContext.tsx @@ -367,6 +367,7 @@ export const ProjectContextProvider: React.FC = ({ children }) => { auditChange("DELETE_ROW", rowId, {}) ); }; + // rowyRun access const _rowyRun: IProjectContext["rowyRun"] = async (args) => { const { service, ...rest } = args; @@ -382,20 +383,7 @@ export const ProjectContextProvider: React.FC = ({ children }) => { ...rest, }); } else { - enqueueSnackbar(`Rowy Run${service ? ` ${service}` : ""} is not set up`, { - variant: "error", - action: ( - - ), - }); - return { success: false, error: "rowyRun is not setup" }; + console.log("Rowy Run is not set up", args); } }; From be84016341fb341f0f576755271ccaf8696dcae0 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Thu, 10 Feb 2022 16:43:48 +1100 Subject: [PATCH 03/18] fix useUpdateCheck throwing error when rowy run not deployed --- src/hooks/useUpdateCheck.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/hooks/useUpdateCheck.ts b/src/hooks/useUpdateCheck.ts index 1da13e04..59e4992e 100644 --- a/src/hooks/useUpdateCheck.ts +++ b/src/hooks/useUpdateCheck.ts @@ -75,11 +75,14 @@ export default function useUpdateCheck() { // Only store the latest release if (compare(resRowy.tag_name, version, ">")) newState.rowy = resRowy; - if (compare(resRowyRun.tag_name, deployedRowyRun.version, ">")) + if ( + deployedRowyRun && + compare(resRowyRun.tag_name, deployedRowyRun.version, ">") + ) newState.rowyRun = resRowyRun; // Save deployed version - newState.deployedRowyRun = deployedRowyRun.version; + newState.deployedRowyRun = deployedRowyRun?.version ?? ""; setLatestUpdate(newState); setLoading(false); From 11d216377895528e2b4dc780045189141c8a86e4 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Thu, 10 Feb 2022 16:44:28 +1100 Subject: [PATCH 04/18] Create table: show text field instead of multiselect for collections when rowy run not deployed --- src/components/TableSettings/form.tsx | 179 ++++++++++++++++--------- src/components/TableSettings/index.tsx | 4 +- 2 files changed, 115 insertions(+), 68 deletions(-) diff --git a/src/components/TableSettings/form.tsx b/src/components/TableSettings/form.tsx index d27229fe..64d27d2d 100644 --- a/src/components/TableSettings/form.tsx +++ b/src/components/TableSettings/form.tsx @@ -7,9 +7,7 @@ import OpenInNewIcon from "@src/components/InlineOpenInNewIcon"; import WarningIcon from "@mui/icons-material/WarningAmber"; import { WIKI_LINKS } from "@src/constants/externalLinks"; -import { name } from "@root/package.json"; import { FieldType as TableFieldType } from "@src/constants/fields"; -import InputAdornment from "@mui/material/InputAdornment"; export const tableSettings = ( mode: TableSettingsDialogModes | null, @@ -18,7 +16,7 @@ export const tableSettings = ( tables: | { label: string; value: any; section: string; collection: string }[] | undefined, - collections: string[] + collections: string[] | null ): Field[] => [ // Step 1: Collection @@ -89,71 +87,120 @@ export const tableSettings = ( ), }, - { - step: "collection", - type: FieldType.singleSelect, - name: "collection", - label: "Collection", - labelPlural: "collections", - options: collections, - itemRenderer: (option) => {option.label}, - freeText: true, - required: true, - assistiveText: ( - <> - {mode === TableSettingsDialogModes.update ? ( + Array.isArray(collections) + ? { + step: "collection", + type: FieldType.singleSelect, + name: "collection", + label: "Collection", + labelPlural: "collections", + options: collections, + itemRenderer: (option) => ( + {option.label} + ), + freeText: true, + required: true, + assistiveText: ( <> - - You can change which Firestore collection to display. Data in the - new collection must be compatible with the existing columns. + {mode === TableSettingsDialogModes.update ? ( + <> + + You can change which Firestore collection to display. Data in + the new collection must be compatible with the existing + columns. + + ) : ( + "Choose which Firestore collection to display." + )}{" "} + + Your collections + + - ) : ( - "Choose which Firestore collection to display." - )}{" "} - - Your collections - - - - ), - AddButtonProps: { - children: "Create collection or use custom path…", - }, - AddDialogProps: { - title: "Create collection or use custom path", - textFieldLabel: ( - <> - Collection name - - If this collection does not exist, it won’t be created until you - add a row to the table - - - ), - }, - TextFieldProps: { - sx: { "& .MuiInputBase-input": { fontFamily: "mono" } }, - }, - // https://firebase.google.com/docs/firestore/quotas#collections_documents_and_fields - validation: [ - ["matches", /^[^\s]+$/, "Collection name cannot have spaces"], - ["notOneOf", [".", ".."], "Collection name cannot be . or .."], - [ - "test", - "double-underscore", - "Collection name cannot begin and end with __", - (value) => !value.startsWith("__") && !value.endsWith("__"), - ], - ], - }, + ), + AddButtonProps: { + children: "Create collection or use custom path…", + }, + AddDialogProps: { + title: "Create collection or use custom path", + textFieldLabel: ( + <> + Collection name + + If this collection does not exist, it won’t be created until + you add a row to the table + + + ), + }, + TextFieldProps: { + sx: { "& .MuiInputBase-input": { fontFamily: "mono" } }, + }, + // https://firebase.google.com/docs/firestore/quotas#collections_documents_and_fields + validation: [ + ["matches", /^[^\s]+$/, "Collection name cannot have spaces"], + ["notOneOf", [".", ".."], "Collection name cannot be . or .."], + [ + "test", + "double-underscore", + "Collection name cannot begin and end with __", + (value) => !value.startsWith("__") && !value.endsWith("__"), + ], + ], + } + : { + step: "collection", + type: FieldType.shortText, + name: "collection", + label: "Collection name", + required: true, + assistiveText: ( + <> + {mode === TableSettingsDialogModes.update ? ( + <> + + You can change which Firestore collection to display. Data in + the new collection must be compatible with the existing + columns. + + ) : ( + "Type the name of the Firestore collection to display." + )}{" "} + + Your collections + + + + ), + sx: { "& .MuiInputBase-input": { fontFamily: "mono" } }, + // https://firebase.google.com/docs/firestore/quotas#collections_documents_and_fields + validation: [ + ["matches", /^[^\s]+$/, "Collection name cannot have spaces"], + ["notOneOf", [".", ".."], "Collection name cannot be . or .."], + [ + "test", + "double-underscore", + "Collection name cannot begin and end with __", + (value) => !value.startsWith("__") && !value.endsWith("__"), + ], + ], + }, // Step 2: Display { diff --git a/src/components/TableSettings/index.tsx b/src/components/TableSettings/index.tsx index bad0b512..8f11b2f8 100644 --- a/src/components/TableSettings/index.tsx +++ b/src/components/TableSettings/index.tsx @@ -54,7 +54,7 @@ export default function TableSettings({ const { data: collections } = useSWR( "firebaseCollections", () => rowyRun?.({ route: runRoutes.listCollections }), - { fallbackData: [], revalidateIfStale: false, dedupingInterval: 60_000 } + { revalidateIfStale: false, dedupingInterval: 60_000 } ); const open = mode !== null; @@ -166,7 +166,7 @@ export default function TableSettings({ })), ["section", "label"] ), - Array.isArray(collections) ? collections.filter((x) => x !== CONFIG) : [] + Array.isArray(collections) ? collections.filter((x) => x !== CONFIG) : null ); const customComponents = { tableId: { From 0c14baba1334323757fea3929c76d82572b6cdca Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Thu, 10 Feb 2022 16:44:36 +1100 Subject: [PATCH 05/18] add rowy run logo to project settings --- src/assets/LogoRowyRun.tsx | 44 +++++++++++++++++++ src/assets/logo-sticker.svg | 35 --------------- .../Settings/ProjectSettings/RowyRun.tsx | 19 ++++---- 3 files changed, 55 insertions(+), 43 deletions(-) create mode 100644 src/assets/LogoRowyRun.tsx delete mode 100644 src/assets/logo-sticker.svg diff --git a/src/assets/LogoRowyRun.tsx b/src/assets/LogoRowyRun.tsx new file mode 100644 index 00000000..c52bcecb --- /dev/null +++ b/src/assets/LogoRowyRun.tsx @@ -0,0 +1,44 @@ +import { SVGProps } from "react"; +import { useTheme } from "@mui/material"; + +export interface ILogoRowyRunProps extends SVGProps { + size?: number; +} + +export default function LogoRowyRun({ + size = 1.5, + ...props +}: ILogoRowyRunProps) { + const theme = useTheme(); + + return ( + + Rowy Run + + + + + + ); +} diff --git a/src/assets/logo-sticker.svg b/src/assets/logo-sticker.svg deleted file mode 100644 index b6696ee0..00000000 --- a/src/assets/logo-sticker.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/components/Settings/ProjectSettings/RowyRun.tsx b/src/components/Settings/ProjectSettings/RowyRun.tsx index 901adf51..01f61073 100644 --- a/src/components/Settings/ProjectSettings/RowyRun.tsx +++ b/src/components/Settings/ProjectSettings/RowyRun.tsx @@ -12,10 +12,10 @@ import LoadingButton from "@mui/lab/LoadingButton"; import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; import CheckCircleIcon from "@mui/icons-material/CheckCircle"; +import LogoRowyRun from "@src/assets/LogoRowyRun"; import { IProjectSettingsChildProps } from "@src/pages/Settings/ProjectSettings"; import { EXTERNAL_LINKS, WIKI_LINKS } from "@src/constants/externalLinks"; import useUpdateCheck from "@src/hooks/useUpdateCheck"; -import { name } from "@root/package.json"; import { runRoutes } from "@src/constants/runRoutes"; export default function RowyRun({ @@ -88,8 +88,12 @@ export default function RowyRun({ return ( <> - - {name} Run is a Cloud Run instance that provides backend functionality, + + + + Rowy Run is a Cloud Run instance that provides backend functionality, such as table action scripts, user management, and easy Cloud Function deployment.{" "} - {name} Run v{latestUpdate.deployedRowyRun} + Rowy Run v{latestUpdate.deployedRowyRun} @@ -166,7 +170,7 @@ export default function RowyRun({ > - If you have not yet deployed {name} Run, click this button and + If you have not yet deployed Rowy Run, click this button and follow the prompts on Cloud Shell. @@ -196,11 +200,10 @@ export default function RowyRun({ color="success" style={{ fontSize: "1rem", verticalAlign: "text-top" }} /> -   - {name} Run is set up correctly +   Rowy Run is set up correctly ) : verified === false ? ( - `${name} Run is not set up correctly` + `Rowy Run is not set up correctly` ) : ( " " ) From d2bf972a548ccd36c2bbab416ea81bbd386b5e8e Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Thu, 10 Feb 2022 16:58:46 +1100 Subject: [PATCH 06/18] remove use of package.json name field --- package.json | 2 +- src/components/Settings/ProjectSettings/About.tsx | 4 ++-- src/components/Setup/Step1RowyRun.tsx | 3 +-- src/components/Setup/Step3Rules.tsx | 6 ++---- src/components/Setup/Step4Migrate.tsx | 7 +++---- src/components/Setup/Step6Finish.tsx | 3 +-- .../ColumnMenu/FieldSettings/DefaultValueInput.tsx | 5 ++--- src/components/TableSettings/DeleteMenu.tsx | 3 +-- src/hooks/useDocumentTitle.ts | 7 ++++--- src/pages/Auth/ImpersonatorAuth.tsx | 3 +-- src/pages/Auth/SetupGuide.tsx | 3 +-- src/pages/Deploy.tsx | 5 ++--- src/pages/Settings/ProjectSettings.tsx | 3 +-- src/pages/Setup.tsx | 12 +++++------- 14 files changed, 27 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index 4ccb2564..e875f985 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "Rowy", + "name": "rowy", "version": "2.3.1", "homepage": "https://rowy.io", "repository": { diff --git a/src/components/Settings/ProjectSettings/About.tsx b/src/components/Settings/ProjectSettings/About.tsx index 4cdd6c5c..db1804db 100644 --- a/src/components/Settings/ProjectSettings/About.tsx +++ b/src/components/Settings/ProjectSettings/About.tsx @@ -7,7 +7,7 @@ import TwitterIcon from "@mui/icons-material/Twitter"; import Logo from "@src/assets/Logo"; import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; -import { name, version } from "@root/package.json"; +import { version } from "@root/package.json"; import { useAppContext } from "@src/contexts/AppContext"; import useUpdateCheck from "@src/hooks/useUpdateCheck"; import { EXTERNAL_LINKS, WIKI_LINKS } from "@src/constants/externalLinks"; @@ -100,7 +100,7 @@ export default function About() { )} - {name} v{version} + Rowy v{version} diff --git a/src/components/Setup/Step1RowyRun.tsx b/src/components/Setup/Step1RowyRun.tsx index bcf4eda5..60c86699 100644 --- a/src/components/Setup/Step1RowyRun.tsx +++ b/src/components/Setup/Step1RowyRun.tsx @@ -9,7 +9,6 @@ import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; import SetupItem from "./SetupItem"; -import { name } from "@root/package.json"; import { rowyRun } from "@src/utils/rowyRun"; import { runRoutes } from "@src/constants/runRoutes"; import { EXTERNAL_LINKS, WIKI_LINKS } from "@src/constants/externalLinks"; @@ -88,7 +87,7 @@ export default function Step1RowyRun({ return ( <> - {name} Run is a Google Cloud Run instance that provides backend + Rowy Run is a Google Cloud Run instance that provides backend functionality, such as table action scripts, user management, and easy Cloud Function deployment. diff --git a/src/components/Setup/Step3Rules.tsx b/src/components/Setup/Step3Rules.tsx index 56ab2509..2e087924 100644 --- a/src/components/Setup/Step3Rules.tsx +++ b/src/components/Setup/Step3Rules.tsx @@ -16,9 +16,7 @@ import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; import SetupItem from "./SetupItem"; import DiffEditor from "@src/components/CodeEditor/DiffEditor"; -import CodeEditor from "@src/components/CodeEditor"; -import { name } from "@root/package.json"; import { useAppContext } from "@src/contexts/AppContext"; import { CONFIG } from "@src/config/dbPaths"; import { @@ -165,8 +163,8 @@ export default function Step3Rules({ return ( <> - {name} configuration is stored in the {CONFIG} collection - on Firestore. Your users will need read access to this collection and + Rowy configuration is stored in the {CONFIG} collection on + Firestore. Your users will need read access to this collection and admins will need write access. diff --git a/src/components/Setup/Step4Migrate.tsx b/src/components/Setup/Step4Migrate.tsx index 676a553d..1e3d0fb7 100644 --- a/src/components/Setup/Step4Migrate.tsx +++ b/src/components/Setup/Step4Migrate.tsx @@ -1,12 +1,11 @@ -import { useState, useEffect } from "react"; +import { useState } from "react"; import { ISetupStepBodyProps } from "@src/pages/Setup"; -import { Typography, Button } from "@mui/material"; +import { Typography } from "@mui/material"; import LoadingButton from "@mui/lab/LoadingButton"; import SetupItem from "./SetupItem"; -import { name } from "@root/package.json"; import { useAppContext } from "@src/contexts/AppContext"; import { CONFIG } from "@src/config/dbPaths"; import { rowyRun } from "@src/utils/rowyRun"; @@ -51,7 +50,7 @@ export default function Step4Migrate({ It looks like you’ve previously configured your Firestore database for Firetable. You can migrate this configuration, including your tables to{" "} - {name}. + Rowy. - You can now continue to {name} and create a table from your Firestore + You can now continue to Rowy and create a table from your Firestore collections. diff --git a/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx b/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx index 78e74b71..1a9c6034 100644 --- a/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx +++ b/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx @@ -12,7 +12,6 @@ import CodeEditorHelper from "@src/components/CodeEditor/CodeEditorHelper"; import FormAutosave from "./FormAutosave"; import { FieldType } from "@src/constants/fields"; import { WIKI_LINKS } from "@src/constants/externalLinks"; -import { name } from "@root/package.json"; const CodeEditor = lazy( () => @@ -81,8 +80,8 @@ export default function DefaultValueInput({ diff --git a/src/components/TableSettings/DeleteMenu.tsx b/src/components/TableSettings/DeleteMenu.tsx index 4179275d..1bc2624a 100644 --- a/src/components/TableSettings/DeleteMenu.tsx +++ b/src/components/TableSettings/DeleteMenu.tsx @@ -9,7 +9,6 @@ import Confirmation from "@src/components/Confirmation"; import { Table } from "@src/contexts/ProjectContext"; import { routes } from "@src/constants/routes"; import { db } from "@src/firebase"; -import { name } from "@root/package.json"; import { SETTINGS, TABLE_SCHEMAS, @@ -106,7 +105,7 @@ export default function DeleteMenu({ clearDialog, data }: IDeleteMenuProps) { body: ( <> - This will only delete the {name} configuration data. + This will only delete the Rowy configuration data. You will not lose any data in your Firestore collection{" "} diff --git a/src/hooks/useDocumentTitle.ts b/src/hooks/useDocumentTitle.ts index f81da18c..2b207c8a 100644 --- a/src/hooks/useDocumentTitle.ts +++ b/src/hooks/useDocumentTitle.ts @@ -1,12 +1,12 @@ import { useEffect } from "react"; -import { name } from "@root/package.json"; export default function useDocumentTitle(projectId: string, title?: string) { useEffect(() => { document.title = [ title, projectId, - name + (window.location.hostname === "localhost" ? " (localhost)" : ""), + "Rowy", + window.location.hostname === "localhost" ? "localhost" : "", ] .filter((x) => x) .join(" • "); @@ -14,7 +14,8 @@ export default function useDocumentTitle(projectId: string, title?: string) { return () => { document.title = [ projectId, - name + (window.location.hostname === "localhost" ? " (localhost)" : ""), + "Rowy", + window.location.hostname === "localhost" ? "localhost" : "", ] .filter((x) => x) .join(" • "); diff --git a/src/pages/Auth/ImpersonatorAuth.tsx b/src/pages/Auth/ImpersonatorAuth.tsx index fc5a1d67..c1a014af 100644 --- a/src/pages/Auth/ImpersonatorAuth.tsx +++ b/src/pages/Auth/ImpersonatorAuth.tsx @@ -10,7 +10,6 @@ import { signOut } from "@src/utils/auth"; import { auth } from "../../firebase"; import { useProjectContext } from "@src/contexts/ProjectContext"; import { runRoutes } from "@src/constants/runRoutes"; -import { name } from "@root/package.json"; export default function ImpersonatorAuthPage() { const { enqueueSnackbar } = useSnackbar(); @@ -58,7 +57,7 @@ export default function ImpersonatorAuthPage() { test permissions and access controls. - Make sure the {name} Run service account has the{" "} + Make sure the Rowy Run service account has the{" "} Service Account Token Creator IAM role. diff --git a/src/pages/Auth/SetupGuide.tsx b/src/pages/Auth/SetupGuide.tsx index 6de420df..5aa46cca 100644 --- a/src/pages/Auth/SetupGuide.tsx +++ b/src/pages/Auth/SetupGuide.tsx @@ -4,7 +4,6 @@ import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; import AuthLayout from "@src/components/Auth/AuthLayout"; import { WIKI_LINKS } from "@src/constants/externalLinks"; -import { name } from "@root/package.json"; export default function AuthSetupGuide() { return ( @@ -12,7 +11,7 @@ export default function AuthSetupGuide() { title="Set up Firebase Authentication" description={ <> - To sign in to {name}, first set up Firebase Authentication in the + To sign in to Rowy, first set up Firebase Authentication in the Firebase Console. } diff --git a/src/pages/Deploy.tsx b/src/pages/Deploy.tsx index 2f890b90..eca61a32 100644 --- a/src/pages/Deploy.tsx +++ b/src/pages/Deploy.tsx @@ -22,7 +22,6 @@ import MarketingBanner from "@src/components/Auth/MarketingBanner"; import AuthLayout from "@src/components/Auth/AuthLayout"; import { EXTERNAL_LINKS, WIKI_LINKS } from "@src/constants/externalLinks"; -import { name } from "@root/package.json"; export default function DeployPage() { const { search } = useLocation(); @@ -51,7 +50,7 @@ export default function DeployPage() { title="Get started" description={ <> - Set up {name} on your Google Cloud or Firebase project with a + Set up Rowy on your Google Cloud or Firebase project with a one-click deploy button.

@@ -148,7 +147,7 @@ export default function DeployPage() { - By setting up {name}, you agree to our{" "} + By setting up Rowy, you agree to our{" "} ; @@ -68,7 +67,7 @@ export default function ProjectSettingsPage() { const sections = [ { title: "About", Component: About }, - { title: `${name} Run`, Component: RowyRun, props: childProps }, + { title: `Rowy Run`, Component: RowyRun, props: childProps }, { title: "Authentication", Component: Authentication, props: childProps }, { title: "Customization", Component: Customization, props: childProps }, ]; diff --git a/src/pages/Setup.tsx b/src/pages/Setup.tsx index a5a19754..44ae341b 100644 --- a/src/pages/Setup.tsx +++ b/src/pages/Setup.tsx @@ -31,13 +31,11 @@ import { SlideTransition } from "@src/components/Modal/SlideTransition"; import Step0Welcome from "@src/components/Setup/Step0Welcome"; import Step1RowyRun, { checkRowyRun } from "@src/components/Setup/Step1RowyRun"; // prettier-ignore -// prettier-ignore import Step2ProjectOwner, { checkProjectOwner } from "@src/components/Setup/Step2ProjectOwner"; import Step3Rules, { checkRules } from "@src/components/Setup/Step3Rules"; import Step4Migrate, { checkMigrate } from "@src/components/Setup/Step4Migrate"; import Step5Finish from "@src/components/Setup/Step6Finish"; -import { name } from "@root/package.json"; import routes from "@src/constants/routes"; import { useAppContext } from "@src/contexts/AppContext"; import { analytics } from "analytics"; @@ -146,7 +144,7 @@ export default function SetupPage() { id: "welcome", layout: "centered" as "centered", shortTitle: "Welcome", - title: `Welcome to ${name}`, + title: `Welcome to Rowy`, body: , actions: completion.welcome ? ( , }, { @@ -194,7 +192,7 @@ export default function SetupPage() { ? { id: "migrate", shortTitle: `Migrate`, - title: `Migrate to ${name} (optional)`, + title: `Migrate to Rowy (optional)`, body: , } : ({} as ISetupStep), @@ -212,7 +210,7 @@ export default function SetupPage() { to={routes.home} sx={{ ml: 1 }} > - Continue to {name} + Continue to Rowy ), }, From af02f50704ed53317c21f131b385d09dff424d5e Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Fri, 11 Feb 2022 17:25:12 +1100 Subject: [PATCH 07/18] add RowyRunModal when user tries to access feature without rowy run set up --- src/App.tsx | 2 + src/atoms/RowyRunModal.ts | 10 ++ src/components/RowyRunModal.tsx | 101 ++++++++++++++++++ .../Settings/UserManagement/InviteUser.tsx | 10 +- .../TableHeader/CloudLogs/index.tsx | 11 +- .../TableHeader/Extensions/index.tsx | 13 ++- src/components/TableHeader/ReExecute.tsx | 14 ++- src/components/TableHeader/Webhooks/index.tsx | 11 +- src/components/fields/Action/ActionFab.tsx | 38 ++++--- src/components/fields/Action/Settings.tsx | 43 ++++---- .../fields/ConnectTable/Settings.tsx | 9 +- src/components/fields/ConnectTable/index.tsx | 3 +- src/components/fields/Derivative/Settings.tsx | 13 ++- src/components/fields/Derivative/index.tsx | 4 +- 14 files changed, 231 insertions(+), 51 deletions(-) create mode 100644 src/atoms/RowyRunModal.ts create mode 100644 src/components/RowyRunModal.tsx diff --git a/src/App.tsx b/src/App.tsx index ba5d5041..5c1aec54 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,6 +12,7 @@ import ErrorBoundary from "@src/components/ErrorBoundary"; import Loading from "@src/components/Loading"; import Navigation from "@src/components/Navigation"; import Logo from "@src/assets/Logo"; +import RowyRunModal from "@src/components/RowyRunModal"; import SwrProvider from "@src/contexts/SwrContext"; import ConfirmationProvider from "@src/components/ConfirmationDialog/Provider"; @@ -65,6 +66,7 @@ export default function App() { + }> { + const [, setOpen] = useAtom(rowyRunModalAtom); + + return (feature: string = "", version: string = "") => + setOpen({ open: true, feature, version }); +}; diff --git a/src/components/RowyRunModal.tsx b/src/components/RowyRunModal.tsx new file mode 100644 index 00000000..5b68bac4 --- /dev/null +++ b/src/components/RowyRunModal.tsx @@ -0,0 +1,101 @@ +import { Link } from "react-router-dom"; +import { useAtom } from "jotai"; +import { rowyRunModalAtom } from "@src/atoms/RowyRunModal"; + +import { + Typography, + Button, + DialogContentText, + Link as MuiLink, +} from "@mui/material"; + +import Modal from "@src/components/Modal"; +import Logo from "@src/assets/LogoRowyRun"; +import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; + +import { useAppContext } from "@src/contexts/AppContext"; +import { routes } from "@src/constants/routes"; +import { WIKI_LINKS } from "@src/constants/externalLinks"; +import { useProjectContext } from "@src/contexts/ProjectContext"; + +export default function RowyRunModal() { + const { userClaims } = useAppContext(); + const { settings } = useProjectContext(); + + const [state, setState] = useAtom(rowyRunModalAtom); + const handleClose = () => setState((s) => ({ ...s, open: false })); + + const showUpdateModal = state.version && settings?.rowyRunUrl; + + return ( + + } + maxWidth="xs" + body={ + <> + + {showUpdateModal ? "Update" : "Set up"} Rowy Run to use{" "} + {state.feature || "this feature"} + + + {showUpdateModal && ( + + {state.feature || "This feature"} requires Rowy Run v + {state.version} or later. + + )} + + + Rowy Run is a Cloud Run instance that provides backend + functionality, such as table action scripts, user management, and + easy Cloud Function deployment.{" "} + + Learn more + + + + + + + {!userClaims?.roles.includes("ADMIN") && ( + + Contact the project owner to set up Rowy Run + + )} + + } + /> + ); +} diff --git a/src/components/Settings/UserManagement/InviteUser.tsx b/src/components/Settings/UserManagement/InviteUser.tsx index 0f534dcd..c51ca215 100644 --- a/src/components/Settings/UserManagement/InviteUser.tsx +++ b/src/components/Settings/UserManagement/InviteUser.tsx @@ -17,10 +17,12 @@ import Modal from "@src/components/Modal"; import { useProjectContext } from "@src/contexts/ProjectContext"; import routes from "@src/constants/routes"; import { runRoutes } from "@src/constants/runRoutes"; +import { useRowyRunModal } from "@src/atoms/RowyRunModal"; export default function InviteUser() { - const { roles: projectRoles, rowyRun } = useProjectContext(); + const { roles: projectRoles, rowyRun, settings } = useProjectContext(); const { enqueueSnackbar } = useSnackbar(); + const openRowyRunModal = useRowyRunModal(); const [open, setOpen] = useState(false); const [status, setStatus] = useState<"LOADING" | string>(""); @@ -49,7 +51,11 @@ export default function InviteUser() { <> + +
+ + Want to invite your teammate to help with setup instead? + + +
); } diff --git a/src/components/Setup/Step1Oauth.tsx b/src/components/Setup/Step1Oauth.tsx new file mode 100644 index 00000000..4a648e38 --- /dev/null +++ b/src/components/Setup/Step1Oauth.tsx @@ -0,0 +1,54 @@ +import { ISetupStepBodyProps } from "@src/pages/Setup"; + +import { Typography, Link, Button } from "@mui/material"; +import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; + +import SetupItem from "./SetupItem"; + +import { WIKI_LINKS } from "@src/constants/externalLinks"; + +export default function Step1Oauth({ + completion, + setCompletion, +}: ISetupStepBodyProps) { + return ( + <> +
+ + Allow Rowy to manage your Firebase Authentication, Firestore database, + and Firebase Storage. + + + Your data and code always stays on your Firebase project.{" "} + + Learn more + + + +
+ + + + + + ); +} diff --git a/src/components/Setup/Step2Project.tsx b/src/components/Setup/Step2Project.tsx new file mode 100644 index 00000000..7dcb7de3 --- /dev/null +++ b/src/components/Setup/Step2Project.tsx @@ -0,0 +1,47 @@ +import { ISetupStepBodyProps } from "@src/pages/Setup"; + +import { + Typography, + TextField, + MenuItem, + Divider, + Button, +} from "@mui/material"; +import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; + +import SetupItem from "./SetupItem"; + +import { WIKI_LINKS } from "@src/constants/externalLinks"; + +export default function Step1Oauth({ + completion, + setCompletion, +}: ISetupStepBodyProps) { + return ( + <> + + Select which Firebase project to set up Rowy on. + + + + + lorem + ipsum + dolor + sit + amet + + + OR + + + + + ); +} diff --git a/src/pages/Setup.tsx b/src/pages/Setup.tsx index 44ae341b..f30e0c5d 100644 --- a/src/pages/Setup.tsx +++ b/src/pages/Setup.tsx @@ -29,7 +29,8 @@ import ScrollableDialogContent from "@src/components/Modal/ScrollableDialogConte import { SlideTransition } from "@src/components/Modal/SlideTransition"; import Step0Welcome from "@src/components/Setup/Step0Welcome"; -import Step1RowyRun, { checkRowyRun } from "@src/components/Setup/Step1RowyRun"; +import Step1Oauth from "@src/components/Setup/Step1Oauth"; +import Step2Project from "@src/components/Setup/Step2Project"; // prettier-ignore import Step2ProjectOwner, { checkProjectOwner } from "@src/components/Setup/Step2ProjectOwner"; import Step3Rules, { checkRules } from "@src/components/Setup/Step3Rules"; @@ -47,7 +48,6 @@ export interface ISetupStep { title: React.ReactNode; description?: React.ReactNode; body: React.ReactNode; - actions?: React.ReactNode; } export interface ISetupStepBodyProps { @@ -69,25 +69,25 @@ const checkAllSteps = async ( console.log("Check all steps"); const completion: Record = {}; - const rowyRunValidation = await checkRowyRun(rowyRunUrl, signal); - if (rowyRunValidation.isValidRowyRunUrl) { - if (rowyRunValidation.isLatestVersion) completion.rowyRun = true; + // const rowyRunValidation = await checkRowyRun(rowyRunUrl, signal); + // if (rowyRunValidation.isValidRowyRunUrl) { + // if (rowyRunValidation.isLatestVersion) completion.rowyRun = true; - const promises = [ - checkProjectOwner(rowyRunUrl, currentUser, userRoles, signal).then( - (projectOwner) => { - if (projectOwner) completion.projectOwner = true; - } - ), - checkRules(rowyRunUrl, authToken, signal).then((rules) => { - if (rules) completion.rules = true; - }), - checkMigrate(rowyRunUrl, authToken, signal).then((requiresMigration) => { - if (requiresMigration) completion.migrate = false; - }), - ]; - await Promise.all(promises); - } + // const promises = [ + // checkProjectOwner(rowyRunUrl, currentUser, userRoles, signal).then( + // (projectOwner) => { + // if (projectOwner) completion.projectOwner = true; + // } + // ), + // checkRules(rowyRunUrl, authToken, signal).then((rules) => { + // if (rules) completion.rules = true; + // }), + // checkMigrate(rowyRunUrl, authToken, signal).then((requiresMigration) => { + // if (requiresMigration) completion.migrate = false; + // }), + // ]; + // await Promise.all(promises); + // } return completion; }; @@ -111,118 +111,34 @@ export default function SetupPage() { rules: false, }); - const [checkingAllSteps, setCheckingAllSteps] = useState(false); - useEffect(() => { - const controller = new AbortController(); - const signal = controller.signal; + // const [checkingAllSteps, setCheckingAllSteps] = useState(false); + // useEffect(() => { + // const controller = new AbortController(); + // const signal = controller.signal; - if (rowyRunUrl) { - setCheckingAllSteps(true); - getAuthToken().then((authToken) => - checkAllSteps( - rowyRunUrl, - currentUser, - userRoles, - authToken, - signal - ).then((result) => { - if (!signal.aborted) { - setCompletion((c) => ({ ...c, ...result })); - setCheckingAllSteps(false); - } - }) - ); - } + // if (rowyRunUrl) { + // setCheckingAllSteps(true); + // getAuthToken().then((authToken) => + // checkAllSteps( + // rowyRunUrl, + // currentUser, + // userRoles, + // authToken, + // signal + // ).then((result) => { + // if (!signal.aborted) { + // setCompletion((c) => ({ ...c, ...result })); + // setCheckingAllSteps(false); + // } + // }) + // ); + // } - return () => controller.abort(); - }, [rowyRunUrl, currentUser, userRoles, getAuthToken]); + // return () => controller.abort(); + // }, [rowyRunUrl, currentUser, userRoles, getAuthToken]); const stepProps = { completion, setCompletion, checkAllSteps, rowyRunUrl }; - const steps: ISetupStep[] = [ - { - id: "welcome", - layout: "centered" as "centered", - shortTitle: "Welcome", - title: `Welcome to Rowy`, - body: , - actions: completion.welcome ? ( - - Get started - - ) : ( - -
- - Get started - -
-
- ), - }, - { - id: "rowyRun", - shortTitle: `Rowy Run`, - title: `Set up Rowy Run`, - body: , - }, - { - id: "projectOwner", - shortTitle: `Project owner`, - title: `Set up project owner`, - body: , - }, - { - id: "rules", - shortTitle: `Rules`, - title: `Set up Firestore Rules`, - body: , - }, - completion.migrate !== undefined - ? { - id: "migrate", - shortTitle: `Migrate`, - title: `Migrate to Rowy (optional)`, - body: , - } - : ({} as ISetupStep), - { - id: "finish", - layout: "centered" as "centered", - shortTitle: `Finish`, - title: `You’re all set up!`, - body: , - actions: ( - - ), - }, - ].filter((x) => x.id); - - const step = - steps.find((step) => step.id === (stepId || steps[0].id)) ?? steps[0]; - const stepIndex = steps.findIndex( - (step) => step.id === (stepId || steps[0].id) - ); - const listedSteps = steps.filter((step) => step.layout !== "centered"); - const handleContinue = () => { let nextIncompleteStepIndex = stepIndex + 1; while (completion[steps[nextIncompleteStepIndex]?.id]) { @@ -235,6 +151,78 @@ export default function SetupPage() { setStepId(nextStepId); }; + const steps: ISetupStep[] = [ + { + id: "welcome", + layout: "centered" as "centered", + shortTitle: "Welcome", + title: `Welcome`, + body: , + actions: <>, + }, + // { + // id: "rowyRun", + // shortTitle: `Rowy Run`, + // title: `Set up Rowy Run`, + // body: , + // }, + // { + // id: "projectOwner", + // shortTitle: `Project owner`, + // title: `Set up project owner`, + // body: , + // }, + { + id: "oauth", + shortTitle: `Access`, + title: `Allow Firebase access`, + body: , + }, + { + id: "project", + shortTitle: `Project`, + title: `Select project`, + body: , + }, + { + id: "rules", + shortTitle: `Firestore Rules`, + title: `Set up Firestore Rules`, + body: , + }, + { + id: "storageRules", + shortTitle: `Storage Rules`, + title: `Set up Firestore Rules`, + body: , + }, + { + id: "finish", + layout: "centered" as "centered", + shortTitle: `Finish`, + title: `You’re all set up!`, + body: , + // actions: ( + // + // ), + }, + ].filter((x) => x.id); + + const step = + steps.find((step) => step.id === (stepId || steps[0].id)) ?? steps[0]; + const stepIndex = steps.findIndex( + (step) => step.id === (stepId || steps[0].id) + ); + const listedSteps = steps.filter((step) => step.layout !== "centered"); + return ( @@ -243,18 +231,17 @@ export default function SetupPage() { elevation={4} sx={{ backgroundColor: (theme) => - alpha(theme.palette.background.paper, 0.5), + alpha(theme.palette.background.paper, 0.75), backdropFilter: "blur(20px) saturate(150%)", maxWidth: BASE_WIDTH, width: (theme) => `calc(100vw - ${theme.spacing(2)})`, - maxHeight: (theme) => + height: (theme) => `calc(${ fullScreenHeight > 0 ? `${fullScreenHeight}px` : "100vh" } - ${theme.spacing( 2 )} - env(safe-area-inset-top) - env(safe-area-inset-bottom))`, - height: BASE_WIDTH * 0.75, resize: "both", p: 0, @@ -393,31 +380,32 @@ export default function SetupPage() { )} -
{ - e.preventDefault(); - try { - handleContinue(); - } catch (e: any) { - throw new Error(e.message); - } - return false; - }} - > - - {step.actions ?? ( - { + e.preventDefault(); + try { + handleContinue(); + } catch (e: any) { + throw new Error(e.message); + } + return false; + }} + > + + + + + )}
); From da94a1c4eb10d20bc6e0139bed081699c5110ca9 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Wed, 16 Feb 2022 16:03:46 +1100 Subject: [PATCH 10/18] restructure SetupPage to allow for multiple flows --- src/App.tsx | 4 +- .../CodeEditor/CodeEditorHelper.tsx | 4 +- src/components/CodeEditor/extensions.d.ts | 2 +- src/components/CodeEditor/utils.d.ts | 2 +- .../StepFinish.tsx} | 25 +- src/components/Setup/BasicSetup/StepRules.tsx | 140 +++++++++ .../Setup/BasicSetup/StepStorageRules.tsx | 112 ++++++++ .../Setup/BasicSetup/StepWelcome.tsx | 91 ++++++ .../StepOauth.tsx} | 18 +- .../Setup/RowyAppSetup/StepProject.tsx | 80 ++++++ .../StepRules.tsx} | 202 +++++++------ .../StepWelcome.tsx} | 34 +-- .../StepRowyRun.tsx} | 37 +-- src/components/Setup/SetupItem.tsx | 13 +- src/components/Setup/SetupLayout.tsx | 269 ++++++++++++++++++ src/components/Setup/Step2Project.tsx | 47 --- src/components/Setup/Step2ProjectOwner.tsx | 200 ------------- src/components/Setup/Step4Migrate.tsx | 112 -------- src/components/Setup/types.d.ts | 15 + src/config/firestoreRules.ts | 18 +- src/config/storageRules.ts | 16 ++ src/pages/Setup/BasicSetup.tsx | 25 ++ src/pages/Setup/RowyAppSetup.tsx | 34 +++ src/pages/{Setup.tsx => Setup/Setup.tsx.old} | 16 +- 24 files changed, 1001 insertions(+), 515 deletions(-) rename src/components/Setup/{Step6Finish.tsx => BasicSetup/StepFinish.tsx} (77%) create mode 100644 src/components/Setup/BasicSetup/StepRules.tsx create mode 100644 src/components/Setup/BasicSetup/StepStorageRules.tsx create mode 100644 src/components/Setup/BasicSetup/StepWelcome.tsx rename src/components/Setup/{Step1Oauth.tsx => RowyAppSetup/StepOauth.tsx} (78%) create mode 100644 src/components/Setup/RowyAppSetup/StepProject.tsx rename src/components/Setup/{Step3Rules.tsx => RowyAppSetup/StepRules.tsx} (66%) rename src/components/Setup/{Step0Welcome.tsx => RowyAppSetup/StepWelcome.tsx} (71%) rename src/components/Setup/{Step1RowyRun.tsx => RowyRunSetup/StepRowyRun.tsx} (88%) create mode 100644 src/components/Setup/SetupLayout.tsx delete mode 100644 src/components/Setup/Step2Project.tsx delete mode 100644 src/components/Setup/Step2ProjectOwner.tsx delete mode 100644 src/components/Setup/Step4Migrate.tsx create mode 100644 src/components/Setup/types.d.ts create mode 100644 src/config/storageRules.ts create mode 100644 src/pages/Setup/BasicSetup.tsx create mode 100644 src/pages/Setup/RowyAppSetup.tsx rename src/pages/{Setup.tsx => Setup/Setup.tsx.old} (96%) diff --git a/src/App.tsx b/src/App.tsx index 5c1aec54..0c5b9683 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -52,7 +52,7 @@ const UserSettingsPage = lazy(() => import("./pages/Settings/UserSettings" /* we // prettier-ignore const UserManagementPage = lazy(() => import("./pages/Settings/UserManagement" /* webpackChunkName: "UserManagementPage" */)); // prettier-ignore -const SetupPage = lazy(() => import("@src/pages/Setup" /* webpackChunkName: "SetupPage" */)); +const BasicSetupPage = lazy(() => import("@src/pages/Setup/BasicSetup" /* webpackChunkName: "BasicSetupPage" */)); export default function App() { return ( @@ -97,7 +97,7 @@ export default function App() { } + render={() => } /> { @@ -66,6 +77,16 @@ export default function Step6Finish() { /> + + ); } diff --git a/src/components/Setup/BasicSetup/StepRules.tsx b/src/components/Setup/BasicSetup/StepRules.tsx new file mode 100644 index 00000000..60f1444f --- /dev/null +++ b/src/components/Setup/BasicSetup/StepRules.tsx @@ -0,0 +1,140 @@ +import { useState } from "react"; +import { useSnackbar } from "notistack"; +import type { ISetupStep, ISetupStepBodyProps } from "../types"; + +import { + Typography, + FormControlLabel, + Checkbox, + Button, + Grid, +} from "@mui/material"; +import CopyIcon from "@src/assets/icons/Copy"; +import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; +import DoneIcon from "@mui/icons-material/Done"; + +import SetupItem from "../SetupItem"; + +import { useAppContext } from "@src/contexts/AppContext"; +import { CONFIG } from "@src/config/dbPaths"; +import { + RULES_START, + RULES_END, + REQUIRED_RULES, + ADMIN_RULES, + RULES_UTILS, +} from "@src/config/firestoreRules"; + +export default { + id: "rules", + shortTitle: "Firestore Rules", + title: "Set up Firestore Rules", + body: StepRules, +} as ISetupStep; + +function StepRules({ isComplete, setComplete }: ISetupStepBodyProps) { + const { projectId } = useAppContext(); + const { enqueueSnackbar } = useSnackbar(); + + const [adminRule, setAdminRule] = useState(true); + + const rules = + RULES_START + + (adminRule ? ADMIN_RULES : "") + + REQUIRED_RULES + + RULES_UTILS + + RULES_END; + + return ( + <> + + Rowy configuration is stored in the {CONFIG} collection on + Firestore. Your users will need read access to this collection and + admins will need write access. + + + + setAdminRule(e.target.checked)} + /> + } + label="Allow admins to read and write all documents" + sx={{ "&&": { ml: -11 / 8, mb: -11 / 8 }, width: "100%" }} + /> + + $1` + ), + }} + /> + +
+ + + + + + + + + +
+
+ + } + onClick={() => setComplete()} + > + Mark as done + + ) + } + status={isComplete ? "complete" : "incomplete"} + /> + + ); +} diff --git a/src/components/Setup/BasicSetup/StepStorageRules.tsx b/src/components/Setup/BasicSetup/StepStorageRules.tsx new file mode 100644 index 00000000..408ffc6e --- /dev/null +++ b/src/components/Setup/BasicSetup/StepStorageRules.tsx @@ -0,0 +1,112 @@ +import { useSnackbar } from "notistack"; +import type { ISetupStep, ISetupStepBodyProps } from "../types"; + +import { Typography, Button, Grid } from "@mui/material"; +import CopyIcon from "@src/assets/icons/Copy"; +import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; +import DoneIcon from "@mui/icons-material/Done"; + +import SetupItem from "../SetupItem"; + +import { useAppContext } from "@src/contexts/AppContext"; +import { + RULES_START, + RULES_END, + REQUIRED_RULES, +} from "@src/config/storageRules"; + +export default { + id: "storageRules", + shortTitle: "Storage Rules", + title: "Set up Firebase Storage Rules", + body: StepStorageRules, +} as ISetupStep; + +const rules = RULES_START + REQUIRED_RULES + RULES_END; + +function StepStorageRules({ isComplete, setComplete }: ISetupStepBodyProps) { + const { projectId } = useAppContext(); + const { enqueueSnackbar } = useSnackbar(); + + return ( + <> + + Image and File fields store files in Firebase Storage. Your users will + need read and write access. + + + + $1` + ), + }} + /> + +
+ + + + + + + + + +
+
+ + } + onClick={() => setComplete()} + sx={{ mt: -0.5 }} + > + Mark as done + + ) + } + status={isComplete ? "complete" : "incomplete"} + /> + + ); +} diff --git a/src/components/Setup/BasicSetup/StepWelcome.tsx b/src/components/Setup/BasicSetup/StepWelcome.tsx new file mode 100644 index 00000000..72ea8208 --- /dev/null +++ b/src/components/Setup/BasicSetup/StepWelcome.tsx @@ -0,0 +1,91 @@ +import type { ISetupStep, ISetupStepBodyProps } from "../types"; + +import { + FormControlLabel, + Checkbox, + Typography, + Link, + Button, +} from "@mui/material"; + +import { EXTERNAL_LINKS } from "@src/constants/externalLinks"; +import { useAppContext } from "@src/contexts/AppContext"; + +export default { + id: "welcome", + layout: "centered", + shortTitle: "Welcome", + title: "Welcome", + body: StepWelcome, +} as ISetupStep; + +function StepWelcome({ isComplete, setComplete }: ISetupStepBodyProps) { + const { projectId } = useAppContext(); + + return ( + <> +
+ + Get started with Rowy in just a few minutes. + + + We have no access to your data and it always stays on your Firebase + project. + + + Project: {projectId} + +
+ + setComplete(e.target.checked)} + /> + } + label={ + <> + I agree to the{" "} + + Terms and Conditions + {" "} + and{" "} + + Privacy Policy + + + } + sx={{ + pr: 1, + textAlign: "left", + alignItems: "flex-start", + p: 0, + m: 0, + }} + /> + + + + ); +} diff --git a/src/components/Setup/Step1Oauth.tsx b/src/components/Setup/RowyAppSetup/StepOauth.tsx similarity index 78% rename from src/components/Setup/Step1Oauth.tsx rename to src/components/Setup/RowyAppSetup/StepOauth.tsx index 4a648e38..b7029cfb 100644 --- a/src/components/Setup/Step1Oauth.tsx +++ b/src/components/Setup/RowyAppSetup/StepOauth.tsx @@ -1,16 +1,20 @@ -import { ISetupStepBodyProps } from "@src/pages/Setup"; +import type { ISetupStep, ISetupStepBodyProps } from "../types"; import { Typography, Link, Button } from "@mui/material"; import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; -import SetupItem from "./SetupItem"; +import SetupItem from "../SetupItem"; import { WIKI_LINKS } from "@src/constants/externalLinks"; -export default function Step1Oauth({ - completion, - setCompletion, -}: ISetupStepBodyProps) { +export default { + id: "oauth", + shortTitle: "Access", + title: "Allow Firebase access", + body: StepOauth, +} as ISetupStep; + +function StepOauth({ isComplete, setComplete }: ISetupStepBodyProps) { return ( <>
@@ -44,7 +48,7 @@ export default function Step1Oauth({ height="20" /> } - onClick={() => setCompletion((c) => ({ ...c, oauth: true }))} + onClick={() => setComplete()} > Sign in with Google diff --git a/src/components/Setup/RowyAppSetup/StepProject.tsx b/src/components/Setup/RowyAppSetup/StepProject.tsx new file mode 100644 index 00000000..1a886ab9 --- /dev/null +++ b/src/components/Setup/RowyAppSetup/StepProject.tsx @@ -0,0 +1,80 @@ +import { ISetupStep, ISetupStepBodyProps } from "../types"; + +import { + useMediaQuery, + Typography, + Stack, + TextField, + MenuItem, + Divider, + Button, +} from "@mui/material"; +import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; + +import SetupItem from "../SetupItem"; + +export default { + id: "project", + shortTitle: "Project", + title: "Select project", + body: StepProject, +} as ISetupStep; + +function StepProject({ isComplete, setComplete }: ISetupStepBodyProps) { + const isMobile = useMediaQuery((theme: any) => theme.breakpoints.down("md")); + + return ( + <> + + Select which Firebase project to set up Rowy on. + + + + + + v || ( + + Select a project… + + ), + }} + > + lorem + ipsum + dolor + sit + amet + + + + OR + + + + + + + ); +} diff --git a/src/components/Setup/Step3Rules.tsx b/src/components/Setup/RowyAppSetup/StepRules.tsx similarity index 66% rename from src/components/Setup/Step3Rules.tsx rename to src/components/Setup/RowyAppSetup/StepRules.tsx index 2e087924..ad7e169c 100644 --- a/src/components/Setup/Step3Rules.tsx +++ b/src/components/Setup/RowyAppSetup/StepRules.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import { ISetupStepBodyProps } from "@src/pages/Setup"; +import type { ISetupStep, ISetupStepBodyProps } from "../types"; import { Typography, @@ -14,24 +14,32 @@ import InfoIcon from "@mui/icons-material/InfoOutlined"; import CopyIcon from "@src/assets/icons/Copy"; import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; -import SetupItem from "./SetupItem"; +import SetupItem from "../SetupItem"; import DiffEditor from "@src/components/CodeEditor/DiffEditor"; import { useAppContext } from "@src/contexts/AppContext"; import { CONFIG } from "@src/config/dbPaths"; import { - requiredRules, - adminRules, - utilFns, - insecureRule, + RULES_START, + RULES_END, + REQUIRED_RULES, + ADMIN_RULES, + RULES_UTILS, + INSECURE_RULES, } from "@src/config/firestoreRules"; import { rowyRun } from "@src/utils/rowyRun"; import { runRoutes } from "@src/constants/runRoutes"; // import { useConfirmation } from "@src/components/ConfirmationDialog"; +export default { + id: "rules", + shortTitle: "Firestore Rules", + title: "Set up Firestore Rules", + body: StepRules, +} as ISetupStep; + const insecureRuleRegExp = new RegExp( - insecureRule - .replace(/\//g, "\\/") + INSECURE_RULES.replace(/\//g, "\\/") .replace(/\*/g, "\\*") .replace(/\s{2,}/g, "\\s+") .replace(/\s/g, "\\s*") @@ -39,25 +47,23 @@ const insecureRuleRegExp = new RegExp( .replace(/;/g, ";?") ); -export default function Step3Rules({ +function StepRules({ rowyRunUrl, - completion, - setCompletion, -}: ISetupStepBodyProps) { + isComplete, + setComplete, +}: ISetupStepBodyProps & { rowyRunUrl: string }) { const { projectId, getAuthToken } = useAppContext(); // const { requestConfirmation } = useConfirmation(); const [error, setError] = useState(false); - const [hasRules, setHasRules] = useState(completion.rules); + const [hasRules, setHasRules] = useState(isComplete); const [adminRule, setAdminRule] = useState(true); const [showManualMode, setShowManualMode] = useState(false); - const rules = `${ - error === "security-rules/not-found" - ? `rules_version = '2';\n\nservice cloud.firestore {\n match /databases/{database}/documents {\n` - : "" - }${adminRule ? adminRules : ""}${requiredRules}${utilFns}${ - error === "security-rules/not-found" ? " }\n}" : "" + const rules = `${error === "security-rules/not-found" ? RULES_START : ""}${ + adminRule ? ADMIN_RULES : "" + }${REQUIRED_RULES}${RULES_UTILS}${ + error === "security-rules/not-found" ? RULES_END : "" }`.replace("\n", ""); const [currentRules, setCurrentRules] = useState(""); @@ -120,7 +126,7 @@ export default function Step3Rules({ if (!res.success) throw new Error(res.message); const isSuccessful = await checkRules(rowyRunUrl, authToken); if (isSuccessful) { - setCompletion((c) => ({ ...c, rules: true })); + setComplete(); setHasRules(true); } setRulesStatus(""); @@ -137,7 +143,7 @@ export default function Step3Rules({ const isSuccessful = await checkRules(rowyRunUrl, authToken); if (isSuccessful) { - setCompletion((c) => ({ ...c, rules: true })); + setComplete(); setHasRules(true); } setRulesStatus(""); @@ -154,7 +160,7 @@ export default function Step3Rules({ // confirm: "Skip", // cancel: "cancel", // handleConfirm: async () => { - // setCompletion((c) => ({ ...c, rules: true })); + // setComplete(); // setHasRules(true); // }, // }); @@ -215,6 +221,7 @@ export default function Step3Rules({ color="primary" onClick={setRules} loading={rulesStatus === "LOADING"} + style={{ position: "sticky", bottom: 8 }} > Set Firestore Rules @@ -236,75 +243,92 @@ export default function Step3Rules({ )} {!hasRules && showManualMode && ( - - + + $1` - ), - }} - /> + "& .comment": { color: "info.dark" }, + }} + dangerouslySetInnerHTML={{ + __html: rules.replace( + /(\/\/.*$)/gm, + `$1` + ), + }} + /> -
- - - +
+ + + + + + + + + + + {rulesStatus !== "LOADING" && + typeof rulesStatus === "string" && ( + + {rulesStatus} + + )} + +
+ - - - - - - - Verify - - {rulesStatus !== "LOADING" && typeof rulesStatus === "string" && ( - - {rulesStatus} - - )} - -
-
-
+ + Verify + + } + > + {rulesStatus !== "LOADING" && typeof rulesStatus === "string" && ( + + {rulesStatus} + + )} + + )} {hasRules && ( @@ -334,10 +358,10 @@ export const checkRules = async ( const sanitizedRules = rules.replace(/\s{2,}/g, " ").replace(/\n/g, " "); const hasRules = sanitizedRules.includes( - requiredRules.replace(/\s{2,}/g, " ").replace(/\n/g, " ") + REQUIRED_RULES.replace(/\s{2,}/g, " ").replace(/\n/g, " ") ) && sanitizedRules.includes( - utilFns.replace(/\s{2,}/g, " ").replace(/\n/g, " ") + RULES_UTILS.replace(/\s{2,}/g, " ").replace(/\n/g, " ") ); return hasRules; } catch (e: any) { diff --git a/src/components/Setup/Step0Welcome.tsx b/src/components/Setup/RowyAppSetup/StepWelcome.tsx similarity index 71% rename from src/components/Setup/Step0Welcome.tsx rename to src/components/Setup/RowyAppSetup/StepWelcome.tsx index 8ade6cd5..4157f585 100644 --- a/src/components/Setup/Step0Welcome.tsx +++ b/src/components/Setup/RowyAppSetup/StepWelcome.tsx @@ -1,4 +1,4 @@ -import { ISetupStepBodyProps } from "@src/pages/Setup"; +import type { ISetupStep, ISetupStepBodyProps } from "../types"; import { FormControlLabel, @@ -8,20 +8,17 @@ import { Button, } from "@mui/material"; -import { useAppContext } from "@src/contexts/AppContext"; import { EXTERNAL_LINKS } from "@src/constants/externalLinks"; -export interface IStep0WelcomeProps extends ISetupStepBodyProps { - handleContinue: () => void; -} - -export default function Step0Welcome({ - completion, - setCompletion, - handleContinue, -}: IStep0WelcomeProps) { - const { projectId } = useAppContext(); +export default { + id: "welcome", + layout: "centered", + shortTitle: "Welcome", + title: "Welcome", + body: StepWelcome, +} as ISetupStep; +function StepWelcome({ isComplete, setComplete }: ISetupStepBodyProps) { return ( <>
@@ -32,18 +29,13 @@ export default function Step0Welcome({ We have no access to your data and it always stays on your Firebase project. - - Project: {projectId} -
- setCompletion((c) => ({ ...c, welcome: e.target.checked })) - } + checked={isComplete} + onChange={(e) => setComplete(e.target.checked)} /> } label={ @@ -83,8 +75,8 @@ export default function Step0Welcome({ variant="contained" color="primary" size="large" - disabled={!completion.welcome} - onClick={handleContinue} + disabled={!isComplete} + type="submit" > Get started diff --git a/src/components/Setup/Step1RowyRun.tsx b/src/components/Setup/RowyRunSetup/StepRowyRun.tsx similarity index 88% rename from src/components/Setup/Step1RowyRun.tsx rename to src/components/Setup/RowyRunSetup/StepRowyRun.tsx index 60c86699..f4d05ac0 100644 --- a/src/components/Setup/Step1RowyRun.tsx +++ b/src/components/Setup/RowyRunSetup/StepRowyRun.tsx @@ -1,32 +1,37 @@ import { useState, useEffect } from "react"; import { useLocation, useHistory } from "react-router-dom"; import queryString from "query-string"; -import { ISetupStepBodyProps } from "@src/pages/Setup"; +import type { ISetupStep, ISetupStepBodyProps } from "../types"; import { Button, Typography, Stack, TextField } from "@mui/material"; import LoadingButton from "@mui/lab/LoadingButton"; import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; -import SetupItem from "./SetupItem"; +import SetupItem from "../SetupItem"; import { rowyRun } from "@src/utils/rowyRun"; import { runRoutes } from "@src/constants/runRoutes"; import { EXTERNAL_LINKS, WIKI_LINKS } from "@src/constants/externalLinks"; -export default function Step1RowyRun({ - completion, - setCompletion, - rowyRunUrl: paramsRowyRunUrl, -}: ISetupStepBodyProps) { +export default { + id: "rowyRun", + shortTitle: "Rowy Run", + title: "Set up Rowy Run", + body: StepRowyRun, +} as ISetupStep; + +function StepRowyRun({ + isComplete, + setComplete, +}: // rowyRunUrl: paramsRowyRunUrl, +ISetupStepBodyProps) { const { pathname } = useLocation(); const history = useHistory(); - const [isValidRowyRunUrl, setIsValidRowyRunUrl] = useState( - completion.rowyRun - ); - const [isLatestVersion, setIsLatestVersion] = useState(completion.rowyRun); + const [isValidRowyRunUrl, setIsValidRowyRunUrl] = useState(isComplete); + const [isLatestVersion, setIsLatestVersion] = useState(isComplete); - const [rowyRunUrl, setRowyRunUrl] = useState(paramsRowyRunUrl); + const [rowyRunUrl, setRowyRunUrl] = useState("paramsRowyRunUrl"); const [latestVersion, setLatestVersion] = useState(""); const [verificationStatus, setVerificationStatus] = useState< "IDLE" | "LOADING" | "FAIL" @@ -45,7 +50,7 @@ export default function Step1RowyRun({ if (result.isLatestVersion) { setIsLatestVersion(true); - setCompletion((c) => ({ ...c, rowyRun: true })); + setComplete(); history.replace({ pathname, search: queryString.stringify({ rowyRunUrl }), @@ -57,9 +62,9 @@ export default function Step1RowyRun({ } }; - useEffect(() => { - if (!isValidRowyRunUrl && paramsRowyRunUrl) console.log(paramsRowyRunUrl); - }, [paramsRowyRunUrl, isValidRowyRunUrl]); + // useEffect(() => { + // if (!isValidRowyRunUrl && paramsRowyRunUrl) console.log(paramsRowyRunUrl); + // }, [paramsRowyRunUrl, isValidRowyRunUrl]); const deployButton = window.location.hostname.includes( EXTERNAL_LINKS.rowyAppHostName diff --git a/src/components/Setup/SetupItem.tsx b/src/components/Setup/SetupItem.tsx index ea099950..3612ccbc 100644 --- a/src/components/Setup/SetupItem.tsx +++ b/src/components/Setup/SetupItem.tsx @@ -31,8 +31,17 @@ export default function SetupItem({ )} - - {title} + + + {title} + {children} diff --git a/src/components/Setup/SetupLayout.tsx b/src/components/Setup/SetupLayout.tsx new file mode 100644 index 00000000..9b3c6bc4 --- /dev/null +++ b/src/components/Setup/SetupLayout.tsx @@ -0,0 +1,269 @@ +import { useState, createElement } from "react"; +import { use100vh } from "react-div-100vh"; +import { SwitchTransition } from "react-transition-group"; +import type { ISetupStep } from "./types"; + +import { + useMediaQuery, + Paper, + Stepper, + Step, + StepButton, + MobileStepper, + IconButton, + Typography, + Stack, + DialogActions, +} from "@mui/material"; +import { alpha } from "@mui/material/styles"; +import LoadingButton from "@mui/lab/LoadingButton"; +import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; +import ChevronRightIcon from "@mui/icons-material/ChevronRight"; + +import BrandedBackground, { Wrapper } from "@src/assets/BrandedBackground"; +import Logo from "@src/assets/Logo"; +import ScrollableDialogContent from "@src/components/Modal/ScrollableDialogContent"; +import { SlideTransition } from "@src/components/Modal/SlideTransition"; + +import { analytics } from "analytics"; + +const BASE_WIDTH = 1024; + +export interface ISetupLayoutProps { + steps: ISetupStep[]; + completion: Record; + setCompletion: React.Dispatch>>; + continueButtonLoading?: boolean; +} + +export default function SetupLayout({ + steps, + completion, + setCompletion, + continueButtonLoading = false, +}: ISetupLayoutProps) { + const fullScreenHeight = use100vh() ?? 0; + const isMobile = useMediaQuery((theme: any) => theme.breakpoints.down("sm")); + + // Store current step’s ID to prevent confusion + const [stepId, setStepId] = useState("welcome"); + // Get current step object + const step = + steps.find((step) => step.id === (stepId || steps[0].id)) ?? steps[0]; + // Get current step index + const stepIndex = steps.indexOf(step); + const listedSteps = steps.filter((step) => step.layout !== "centered"); + + // Continue goes to the next incomplete step + const handleContinue = () => { + let nextIncompleteStepIndex = stepIndex + 1; + while (completion[steps[nextIncompleteStepIndex]?.id]) { + // console.log("iteration", steps[nextIncompleteStepIndex]?.id); + nextIncompleteStepIndex++; + } + + const nextStepId = steps[nextIncompleteStepIndex].id; + analytics.logEvent("setup_step", { step: nextStepId }); + setStepId(nextStepId); + }; + + // Inject props into step.body + const body = createElement(step.body, { + completion, + setCompletion, + isComplete: completion[step.id], + setComplete: (value: boolean = true) => + setCompletion((c) => ({ ...c, [step.id]: value })), + }); + + return ( + + +
{ + e.preventDefault(); + try { + handleContinue(); + } catch (e: any) { + throw new Error(e.message); + } + return false; + }} + > + + alpha(theme.palette.background.paper, 0.75), + backdropFilter: "blur(20px) saturate(150%)", + + maxWidth: BASE_WIDTH, + width: (theme) => `calc(100vw - ${theme.spacing(2)})`, + height: (theme) => + `calc(${ + fullScreenHeight > 0 ? `${fullScreenHeight}px` : "100vh" + } - ${theme.spacing( + 2 + )} - env(safe-area-inset-top) - env(safe-area-inset-bottom))`, + resize: "both", + + p: 0, + "& > *, & > .MuiDialogContent-root": { px: { xs: 2, sm: 4 } }, + display: "flex", + flexDirection: "column", + + "& .MuiTypography-inherit, & .MuiDialogContent-root": { + typography: "body1", + }, + + "& p": { + maxWidth: "70ch", + }, + }} + > + {stepId === "welcome" ? null : !isMobile ? ( + + {listedSteps.map(({ id, shortTitle }, i) => ( + + setStepId(id)} + disabled={i > 0 && !completion[listedSteps[i - 1]?.id]} + sx={{ py: 2, my: -2, borderRadius: 1 }} + > + {shortTitle} + + + ))} + + ) : ( + setStepId(steps[stepIndex - 1].id)} + > + + + } + nextButton={ + setStepId(steps[stepIndex + 1].id)} + > + + + } + position="static" + sx={{ + background: "none", + p: 0, + "& .MuiMobileStepper-dot": { mx: 0.5 }, + }} + /> + )} + + {step.layout === "centered" ? ( + + + {stepId === "welcome" && ( + + + + + + )} + + + + + {step.title} + + + + + + + + {body} + + + + + + ) : ( + <> + + + + {step.title} + + + + + + + + {body} + + + + + )} + + {step.layout !== "centered" && ( + + + Continue + + + )} + +
+
+ ); +} diff --git a/src/components/Setup/Step2Project.tsx b/src/components/Setup/Step2Project.tsx deleted file mode 100644 index 7dcb7de3..00000000 --- a/src/components/Setup/Step2Project.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { ISetupStepBodyProps } from "@src/pages/Setup"; - -import { - Typography, - TextField, - MenuItem, - Divider, - Button, -} from "@mui/material"; -import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; - -import SetupItem from "./SetupItem"; - -import { WIKI_LINKS } from "@src/constants/externalLinks"; - -export default function Step1Oauth({ - completion, - setCompletion, -}: ISetupStepBodyProps) { - return ( - <> - - Select which Firebase project to set up Rowy on. - - - - - lorem - ipsum - dolor - sit - amet - - - OR - - - - - ); -} diff --git a/src/components/Setup/Step2ProjectOwner.tsx b/src/components/Setup/Step2ProjectOwner.tsx deleted file mode 100644 index b61ee872..00000000 --- a/src/components/Setup/Step2ProjectOwner.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import { useState, useEffect } from "react"; -import { ISetupStepBodyProps } from "@src/pages/Setup"; - -import { Typography, Stack, Button, IconButton } from "@mui/material"; -import LoadingButton from "@mui/lab/LoadingButton"; -import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; - -import SetupItem from "./SetupItem"; -import SignInWithGoogle from "./SignInWithGoogle"; - -import { useAppContext } from "@src/contexts/AppContext"; -import { rowyRun } from "@src/utils/rowyRun"; -import { runRoutes } from "@src/constants/runRoutes"; -import CopyIcon from "@src/assets/icons/Copy"; - -export default function Step2ProjectOwner({ - rowyRunUrl, - completion, - setCompletion, -}: ISetupStepBodyProps) { - const { projectId, currentUser, getAuthToken } = useAppContext(); - - const [email, setEmail] = useState(""); - useEffect(() => { - rowyRun({ serviceUrl: rowyRunUrl, route: runRoutes.projectOwner }) - .then((data) => setEmail(data.email)) - .catch((e: any) => { - console.error(e); - alert(`Failed to get project owner email: ${e.message}`); - }); - }, [rowyRunUrl]); - - const [isDomainAuthorized, setIsDomainAuthorized] = useState( - !!currentUser || completion.projectOwner - ); - const isSignedIn = currentUser?.email?.toLowerCase() === email.toLowerCase(); - const [hasRoles, setHasRoles] = useState( - completion.projectOwner - ); - - const setRoles = async () => { - setHasRoles("LOADING"); - try { - const authToken = await getAuthToken(); - const res = await rowyRun({ - route: runRoutes.setOwnerRoles, - serviceUrl: rowyRunUrl, - authToken, - }); - - if (!res.success) - throw new Error(`${res.message}. Project owner: ${res.ownerEmail}`); - - setHasRoles(true); - setCompletion((c) => ({ ...c, projectOwner: true })); - } catch (e: any) { - console.error(e); - setHasRoles(e.message); - } - }; - - return ( - <> - - The project owner requires full access to manage this project. The - default project owner is the Google Cloud account used to deploy Rowy - Run: {email} - - - - {!(isSignedIn || isDomainAuthorized) && ( - <> -
    -
  1. the Google auth provider enabled and
  2. -
  3. - this domain authorized:{" "} - {window.location.hostname} - - navigator.clipboard.writeText(window.location.hostname) - } - > - - -
  4. -
- - - - - - - - )} -
- - {isDomainAuthorized && ( - - Sign in as the project owner: {email} - - ) - } - > - {!isSignedIn && ( - - )} - - )} - - {isSignedIn && ( - - {hasRoles !== true && ( -
- - Assign roles - - - {typeof hasRoles === "string" && hasRoles !== "LOADING" && ( - - {hasRoles} - - )} -
- )} -
- )} - - ); -} - -export const checkProjectOwner = async ( - rowyRunUrl: string, - currentUser: firebase.default.User | null | undefined, - userRoles: string[] | null, - signal?: AbortSignal -) => { - if (!currentUser || !Array.isArray(userRoles)) return false; - - try { - const res = await rowyRun({ - serviceUrl: rowyRunUrl, - route: runRoutes.projectOwner, - signal, - }); - const email = res.email; - if (currentUser.email !== email) return false; - return userRoles.includes("ADMIN") && userRoles.includes("OWNER"); - } catch (e: any) { - console.error(e); - return false; - } -}; diff --git a/src/components/Setup/Step4Migrate.tsx b/src/components/Setup/Step4Migrate.tsx deleted file mode 100644 index 1e3d0fb7..00000000 --- a/src/components/Setup/Step4Migrate.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { useState } from "react"; -import { ISetupStepBodyProps } from "@src/pages/Setup"; - -import { Typography } from "@mui/material"; -import LoadingButton from "@mui/lab/LoadingButton"; - -import SetupItem from "./SetupItem"; - -import { useAppContext } from "@src/contexts/AppContext"; -import { CONFIG } from "@src/config/dbPaths"; -import { rowyRun } from "@src/utils/rowyRun"; -import { runRoutes } from "@src/constants/runRoutes"; - -export default function Step4Migrate({ - rowyRunUrl, - completion, - setCompletion, -}: ISetupStepBodyProps) { - const { getAuthToken } = useAppContext(); - - const [status, setStatus] = useState<"LOADING" | boolean | string>( - completion.migrate - ); - - const migrate = async () => { - setStatus("LOADING"); - try { - const authToken = await getAuthToken(); - - const res = await rowyRun({ - route: runRoutes.migrateFT2Rowy, - serviceUrl: rowyRunUrl, - authToken, - }); - if (!res.success) throw new Error(res.message); - - const check = await checkMigrate(rowyRunUrl, authToken); - if (!check.migrationRequired) { - setCompletion((c) => ({ ...c, migrate: true })); - setStatus(true); - } - } catch (e: any) { - console.error(e); - setStatus(e.message); - } - }; - - return ( - <> - - It looks like you’ve previously configured your Firestore database for - Firetable. You can migrate this configuration, including your tables to{" "} - Rowy. - - - - Configuration migrated to the {CONFIG} collection. - - ) : ( - <> - Migrate your configuration to the {CONFIG}{" "} - collection. - - ) - } - > - {status !== true && ( - <> - - Migrate - - {status !== "LOADING" && typeof status === "string" && ( - - {status} - - )} - - )} - - - ); -} - -export const checkMigrate = async ( - rowyRunUrl: string, - authToken: string, - signal?: AbortSignal -) => { - if (!authToken) return false; - - try { - const res = await rowyRun({ - serviceUrl: rowyRunUrl, - route: runRoutes.checkFT2Rowy, - authToken, - signal, - }); - return res.migrationRequired; - } catch (e: any) { - console.error(e); - return false; - } -}; diff --git a/src/components/Setup/types.d.ts b/src/components/Setup/types.d.ts new file mode 100644 index 00000000..a4e3037f --- /dev/null +++ b/src/components/Setup/types.d.ts @@ -0,0 +1,15 @@ +export interface ISetupStep { + id: string; + layout?: "centered"; + shortTitle: string; + title: React.ReactNode; + description?: React.ReactNode; + body: React.ComponentType; +} + +export interface ISetupStepBodyProps { + completion: Record; + setCompletion: React.Dispatch>>; + isComplete: boolean; + setComplete: (value: boolean = true) => void; +} diff --git a/src/config/firestoreRules.ts b/src/config/firestoreRules.ts index e23047bd..8164abdf 100644 --- a/src/config/firestoreRules.ts +++ b/src/config/firestoreRules.ts @@ -1,6 +1,16 @@ import { CONFIG, USERS, PUBLIC_SETTINGS } from "./dbPaths"; -export const requiredRules = ` +export const RULES_START = `rules_version = '2'; + +service cloud.firestore { + match /databases/{database}/documents { +`; + +export const RULES_END = ` + } +}`; + +export const REQUIRED_RULES = ` // Rowy: Allow signed in users to read Rowy configuration and admins to write match /${CONFIG}/{docId} { allow read: if request.auth != null; @@ -21,14 +31,14 @@ export const requiredRules = ` } ` as const; -export const adminRules = ` +export const ADMIN_RULES = ` // Allow admins to read and write all documents match /{document=**} { allow read, write: if hasAnyRole(["ADMIN", "OWNER"]); } ` as const; -export const utilFns = ` +export const RULES_UTILS = ` // Rowy: Utility functions function isDocOwner(docId) { return request.auth != null && (request.auth.uid == resource.id || request.auth.uid == docId); @@ -38,7 +48,7 @@ export const utilFns = ` } ` as const; -export const insecureRule = ` +export const INSECURE_RULES = ` match /{document=**} { allow read, write: if true; } diff --git a/src/config/storageRules.ts b/src/config/storageRules.ts new file mode 100644 index 00000000..5abdd083 --- /dev/null +++ b/src/config/storageRules.ts @@ -0,0 +1,16 @@ +export const RULES_START = `rules_version = '2'; + +service firebase.storage { + match /b/{bucket}/o { +`; + +export const RULES_END = ` + } +}`; + +export const REQUIRED_RULES = ` + // Rowy: Allow signed in users with Roles to read and write to Storage + match /{allPaths=**} { + allow read, write: if request.auth.token.roles.size() > 0; + } +`; diff --git a/src/pages/Setup/BasicSetup.tsx b/src/pages/Setup/BasicSetup.tsx new file mode 100644 index 00000000..7c59d967 --- /dev/null +++ b/src/pages/Setup/BasicSetup.tsx @@ -0,0 +1,25 @@ +import { useState } from "react"; + +import SetupLayout from "@src/components/Setup/SetupLayout"; +import StepWelcome from "@src/components/Setup/BasicSetup/StepWelcome"; +import StepRules from "@src/components/Setup/BasicSetup/StepRules"; +import StepStorageRules from "@src/components/Setup/BasicSetup/StepStorageRules"; +import StepFinish from "@src/components/Setup/BasicSetup/StepFinish"; + +const steps = [StepWelcome, StepRules, StepStorageRules, StepFinish]; + +export default function BasicSetupPage() { + const [completion, setCompletion] = useState>({ + welcome: false, + rules: false, + storageRules: false, + }); + + return ( + + ); +} diff --git a/src/pages/Setup/RowyAppSetup.tsx b/src/pages/Setup/RowyAppSetup.tsx new file mode 100644 index 00000000..07242d42 --- /dev/null +++ b/src/pages/Setup/RowyAppSetup.tsx @@ -0,0 +1,34 @@ +import { useState } from "react"; + +import SetupLayout from "@src/components/Setup/SetupLayout"; +import StepWelcome from "@src/components/Setup/RowyAppSetup/StepWelcome"; +import StepOauth from "@src/components/Setup/RowyAppSetup/StepOauth"; +import StepProject from "@src/components/Setup/RowyAppSetup/StepProject"; +import StepRules from "@src/components/Setup/RowyAppSetup/StepRules"; +import StepStorageRules from "@src/components/Setup/BasicSetup/StepStorageRules"; +import StepFinish from "@src/components/Setup/BasicSetup/StepFinish"; + +const steps = [ + StepWelcome, + StepOauth, + StepProject, + StepRules, + StepStorageRules, + StepFinish, +]; + +export default function RowyAppSetupPage() { + const [completion, setCompletion] = useState>({ + welcome: false, + rules: false, + storageRules: false, + }); + + return ( + + ); +} diff --git a/src/pages/Setup.tsx b/src/pages/Setup/Setup.tsx.old similarity index 96% rename from src/pages/Setup.tsx rename to src/pages/Setup/Setup.tsx.old index f30e0c5d..77746075 100644 --- a/src/pages/Setup.tsx +++ b/src/pages/Setup/Setup.tsx.old @@ -28,12 +28,11 @@ import Logo from "@src/assets/Logo"; import ScrollableDialogContent from "@src/components/Modal/ScrollableDialogContent"; import { SlideTransition } from "@src/components/Modal/SlideTransition"; -import Step0Welcome from "@src/components/Setup/Step0Welcome"; +import StepWelcome from "@src/components/Setup/RowyAppSetup/StepWelcome"; import Step1Oauth from "@src/components/Setup/Step1Oauth"; -import Step2Project from "@src/components/Setup/Step2Project"; // prettier-ignore import Step2ProjectOwner, { checkProjectOwner } from "@src/components/Setup/Step2ProjectOwner"; -import Step3Rules, { checkRules } from "@src/components/Setup/Step3Rules"; +// import Step3Rules, { checkRules } from "@src/components/Setup/Step3Rules"; import Step4Migrate, { checkMigrate } from "@src/components/Setup/Step4Migrate"; import Step5Finish from "@src/components/Setup/Step6Finish"; @@ -157,8 +156,7 @@ export default function SetupPage() { layout: "centered" as "centered", shortTitle: "Welcome", title: `Welcome`, - body: , - actions: <>, + body: , }, // { // id: "rowyRun", @@ -182,19 +180,19 @@ export default function SetupPage() { id: "project", shortTitle: `Project`, title: `Select project`, - body: , + body: , }, { id: "rules", shortTitle: `Firestore Rules`, title: `Set up Firestore Rules`, - body: , + body: , }, { id: "storageRules", shortTitle: `Storage Rules`, title: `Set up Firestore Rules`, - body: , + body: , }, { id: "finish", @@ -214,7 +212,7 @@ export default function SetupPage() { // // ), }, - ].filter((x) => x.id); + ]; const step = steps.find((step) => step.id === (stepId || steps[0].id)) ?? steps[0]; From 4676503807c45f050cf0e307164401b4dd0099c0 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Fri, 18 Feb 2022 14:44:14 +1100 Subject: [PATCH 11/18] SetupLayout: add separate animation for description --- package.json | 8 +- .../Setup/BasicSetup/StepFinish.tsx | 7 +- src/components/Setup/BasicSetup/StepRules.tsx | 13 +- .../Setup/BasicSetup/StepStorageRules.tsx | 7 +- .../Setup/BasicSetup/StepWelcome.tsx | 24 +-- .../Setup/RowyAppSetup/StepWelcome.tsx | 19 +- .../Setup/RowyRunSetup/StepRowyRun.tsx | 8 +- src/components/Setup/SetupLayout.tsx | 38 ++-- yarn.lock | 168 +++++++++--------- 9 files changed, 149 insertions(+), 143 deletions(-) diff --git a/package.json b/package.json index 54fe2d19..e8539ccf 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,10 @@ "@hookform/resolvers": "^2.8.5", "@mdi/js": "^6.5.95", "@monaco-editor/react": "^4.3.1", - "@mui/icons-material": "^5.4.1", - "@mui/lab": "^5.0.0-alpha.68", - "@mui/material": "^5.4.1", - "@mui/styles": "^5.4.1", + "@mui/icons-material": "^5.4.2", + "@mui/lab": "^5.0.0-alpha.69", + "@mui/material": "^5.4.2", + "@mui/styles": "^5.4.2", "@rowy/form-builder": "^0.5.3", "@rowy/multiselect": "^0.2.3", "@tinymce/tinymce-react": "^3.12.6", diff --git a/src/components/Setup/BasicSetup/StepFinish.tsx b/src/components/Setup/BasicSetup/StepFinish.tsx index bab976ec..24ab4f67 100644 --- a/src/components/Setup/BasicSetup/StepFinish.tsx +++ b/src/components/Setup/BasicSetup/StepFinish.tsx @@ -18,6 +18,8 @@ export default { layout: "centered", shortTitle: "Finish", title: "You’re all set up!", + description: + "You can now continue to Rowy and create a table from your Firestore collections.", body: StepFinish, } as ISetupStep; @@ -37,11 +39,6 @@ function StepFinish() { return ( <> - - You can now continue to Rowy and create a table from your Firestore - collections. - - + Rowy configuration is stored in the {CONFIG} collection on + Firestore. Your users will need read access to this collection and admins + will need write access. + + ), body: StepRules, } as ISetupStep; @@ -47,12 +54,6 @@ function StepRules({ isComplete, setComplete }: ISetupStepBodyProps) { return ( <> - - Rowy configuration is stored in the {CONFIG} collection on - Firestore. Your users will need read access to this collection and - admins will need write access. - - - - Image and File fields store files in Firebase Storage. Your users will - need read and write access. - - + Get started with Rowy in just a few minutes. +
+
+ We have no access to your data and it always stays on your Firebase + project. + + ), body: StepWelcome, } as ISetupStep; @@ -24,18 +33,9 @@ function StepWelcome({ isComplete, setComplete }: ISetupStepBodyProps) { return ( <> -
- - Get started with Rowy in just a few minutes. - - - We have no access to your data and it always stays on your Firebase - project. - - - Project: {projectId} - -
+ + Project: {projectId} + + Get started with Rowy in just a few minutes. +
+
+ We have no access to your data and it always stays on your Firebase + project. + + ), body: StepWelcome, } as ISetupStep; function StepWelcome({ isComplete, setComplete }: ISetupStepBodyProps) { return ( <> -
- - Get started with Rowy in just a few minutes. - - - We have no access to your data and it always stays on your Firebase - project. - -
- - - Rowy Run is a Google Cloud Run instance that provides backend - functionality, such as table action scripts, user management, and easy - Cloud Function deployment. - - {stepId === "welcome" && ( - + )} - + + + + + {step.description} + + + + @@ -235,16 +243,24 @@ export default function SetupLayout({ - - - + + + + + {step.description} + + + + + + {body} - - - + + + )} diff --git a/yarn.lock b/yarn.lock index badf1454..343692c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1654,10 +1654,10 @@ resolved "https://registry.yarnpkg.com/@date-io/core/-/core-1.3.13.tgz#90c71da493f20204b7a972929cc5c482d078b3fa" integrity sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA== -"@date-io/core@^2.11.0": - version "2.11.0" - resolved "https://registry.yarnpkg.com/@date-io/core/-/core-2.11.0.tgz#28580cda1c8228ab2c7ed6aee673ef0495f913e6" - integrity sha512-DvPBnNoeuLaoSJZaxgpu54qzRhRKjSYVyQjhznTFrllKuDpm0sDFjHo6lvNLCM/cfMx2gb2PM2zY2kc9C8nmuw== +"@date-io/core@^2.13.1": + version "2.13.1" + resolved "https://registry.yarnpkg.com/@date-io/core/-/core-2.13.1.tgz#f041765aff5c55fbc7e37fdd75fc1792733426d6" + integrity sha512-pVI9nfkf2qClb2Cxdq0Q4zJhdawMG4ybWZUVGifT78FDwzRMX2SwXBb55s5NRJk0HcIicDuxktmCtemZqMH1Zg== "@date-io/date-fns@1.x": version "1.3.13" @@ -1666,33 +1666,33 @@ dependencies: "@date-io/core" "^1.3.13" -"@date-io/date-fns@^2.11.0": - version "2.11.0" - resolved "https://registry.yarnpkg.com/@date-io/date-fns/-/date-fns-2.11.0.tgz#142fbf954eda7ad66514af7a2802d78c4ea40053" - integrity sha512-mPQ71plBeFrArvBSHtjWMHXA89IUbZ6kuo2dsjlRC/1uNOybo91spIb+wTu03NxKTl8ut07s0jJ9svF71afpRg== +"@date-io/date-fns@^2.13.1": + version "2.13.1" + resolved "https://registry.yarnpkg.com/@date-io/date-fns/-/date-fns-2.13.1.tgz#19d8a245dab61c03c95ba492d679d98d2b0b4af5" + integrity sha512-8fmfwjiLMpFLD+t4NBwDx0eblWnNcgt4NgfT/uiiQTGI81fnPu9tpBMYdAcuWxaV7LLpXgzLBx1SYWAMDVUDQQ== dependencies: - "@date-io/core" "^2.11.0" + "@date-io/core" "^2.13.1" -"@date-io/dayjs@^2.11.0": - version "2.11.0" - resolved "https://registry.yarnpkg.com/@date-io/dayjs/-/dayjs-2.11.0.tgz#41f4b4b9629612e6012accffd848875d1aeffb74" - integrity sha512-w67vRK56NZJIKhJM/CrNbfnIcuMvR3ApfxzNZiCZ5w29sxgBDeKuX4M+P7A9r5HXOMGcsOcpgaoTDINNGkdpGQ== +"@date-io/dayjs@^2.13.1": + version "2.13.1" + resolved "https://registry.yarnpkg.com/@date-io/dayjs/-/dayjs-2.13.1.tgz#98461d22ee98179b9f2dca3b36f1b618704ae593" + integrity sha512-5bL4WWWmlI4uGZVScANhHJV7Mjp93ec2gNeUHDqqLaMZhp51S0NgD25oqj/k0LqBn1cdU2MvzNpk/ObMmVv5cQ== dependencies: - "@date-io/core" "^2.11.0" + "@date-io/core" "^2.13.1" -"@date-io/luxon@^2.11.1": - version "2.11.1" - resolved "https://registry.yarnpkg.com/@date-io/luxon/-/luxon-2.11.1.tgz#31a72f7b5e163c74e8a3b29d8f16c4c30de6ed43" - integrity sha512-JUXo01kdPQxLORxqdENrgdUhooKgDUggsNRSdi2BcUhASIY2KGwwWXu8ikVHHGkw+DUF4FOEKGfkQd0RHSvX6g== +"@date-io/luxon@^2.13.1": + version "2.13.1" + resolved "https://registry.yarnpkg.com/@date-io/luxon/-/luxon-2.13.1.tgz#3701b3cabfffda5102af302979aa6e58acfda91a" + integrity sha512-yG+uM7lXfwLyKKEwjvP8oZ7qblpmfl9gxQYae55ifbwiTs0CoCTkYkxEaQHGkYtTqGTzLqcb0O9Pzx6vgWg+yg== dependencies: - "@date-io/core" "^2.11.0" + "@date-io/core" "^2.13.1" -"@date-io/moment@^2.11.0": - version "2.11.0" - resolved "https://registry.yarnpkg.com/@date-io/moment/-/moment-2.11.0.tgz#850f8dd090d401845b39276d034dbabe20224ef5" - integrity sha512-QSL+83qezQ9Ty0dtFgAkk6eC0GMl/lgYfDajeVUDB3zVA2A038hzczRLBg29ifnBGhQMPABxuOafgWwhDjlarg== +"@date-io/moment@^2.13.1": + version "2.13.1" + resolved "https://registry.yarnpkg.com/@date-io/moment/-/moment-2.13.1.tgz#122a51e4bdedf71ff3babb264427737dc022c1e6" + integrity sha512-XX1X/Tlvl3TdqQy2j0ZUtEJV6Rl8tOyc5WOS3ki52He28Uzme4Ro/JuPWTMBDH63weSWIZDlbR7zBgp3ZA2y1A== dependencies: - "@date-io/core" "^2.11.0" + "@date-io/core" "^2.13.1" "@emotion/babel-plugin@^11.3.0": version "11.3.0" @@ -2421,55 +2421,55 @@ "@monaco-editor/loader" "^1.2.0" prop-types "^15.7.2" -"@mui/base@5.0.0-alpha.68": - version "5.0.0-alpha.68" - resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.68.tgz#d93d77e662bc8dce47c9415fc6cbcac6658efab7" - integrity sha512-q+3gX6EHuM/AyOn8fkoANQxSzIHBeuNsrGgb7SPP0y7NuM+4ZHG/b9882+OfHcilaSqPDWUQoLbphcBpw/m/RA== +"@mui/base@5.0.0-alpha.69": + version "5.0.0-alpha.69" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.69.tgz#8511198d760de0795870f5ec63e53db73ba801ec" + integrity sha512-IxUUj/lkilCTNBIybQxyQGW/zpxFp490G0QBQJgRp9TJkW2PWSTLvAH7gcH0YHd0L2TAf1TRgfdemoRseMzqQA== dependencies: "@babel/runtime" "^7.17.0" "@emotion/is-prop-valid" "^1.1.1" - "@mui/utils" "^5.4.1" + "@mui/utils" "^5.4.2" "@popperjs/core" "^2.4.4" clsx "^1.1.1" prop-types "^15.7.2" react-is "^17.0.2" -"@mui/icons-material@^5.4.1": - version "5.4.1" - resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.4.1.tgz#20901e9a09154355b7a832180a90717938c675c4" - integrity sha512-koiq9q2GfjXRUWcC5fEi1b+EA4vfJHgIaAdBHlkOrBx2cnmmazQcyib501eodPfaZGx9BikrhivODaNQYQq8hA== +"@mui/icons-material@^5.4.2": + version "5.4.2" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.4.2.tgz#b2fd2c6c81d2d275e17ce40bd50c63cb197d324b" + integrity sha512-7c+G3jBT+e+pN0a9DJ0Bd8Kr1Vy6os5Q1yd2aXcwuhlRI3uzJBLJ8sX6FSWoh5DSEBchb7Bsk1uHz6U0YN9l+Q== dependencies: "@babel/runtime" "^7.17.0" -"@mui/lab@^5.0.0-alpha.68": - version "5.0.0-alpha.68" - resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.68.tgz#a5034a8f749f3f4f0a1b8613515bed4054ade3e6" - integrity sha512-wvszkLsgXgl3kMPVpHNm9pRYld9/2r0MYRlJUEh2GWwjBPE3dDTOIF2IHgZ3WqRBnJMitzUVt7v5Lu9/grjrIQ== +"@mui/lab@^5.0.0-alpha.69": + version "5.0.0-alpha.69" + resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.69.tgz#c1130e6ed5edc579c5d88a762e79935b01181cb1" + integrity sha512-VrTcmXTS9UlTsp40nIZ/R/HBHOvxP2lvgSY9zLSn5XPhMQEMD1H0wTJ68mEuCm18cnl1sbcUOCMfWx9io/u5zg== dependencies: "@babel/runtime" "^7.17.0" - "@date-io/date-fns" "^2.11.0" - "@date-io/dayjs" "^2.11.0" - "@date-io/luxon" "^2.11.1" - "@date-io/moment" "^2.11.0" - "@mui/base" "5.0.0-alpha.68" - "@mui/system" "^5.4.1" - "@mui/utils" "^5.4.1" + "@date-io/date-fns" "^2.13.1" + "@date-io/dayjs" "^2.13.1" + "@date-io/luxon" "^2.13.1" + "@date-io/moment" "^2.13.1" + "@mui/base" "5.0.0-alpha.69" + "@mui/system" "^5.4.2" + "@mui/utils" "^5.4.2" clsx "^1.1.1" prop-types "^15.7.2" react-is "^17.0.2" react-transition-group "^4.4.2" rifm "^0.12.1" -"@mui/material@^5.4.1": - version "5.4.1" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.4.1.tgz#05d3f726771c413dc430163d7c508edfcee04807" - integrity sha512-SxAT43UAjFTBBpJrN+oGrv40xP1uCa5Z49NfHt3m93xYeFzbxKOk0V9IKU7zlUjbsaVQ0i+o24yF5GULZmynlA== +"@mui/material@^5.4.2": + version "5.4.2" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.4.2.tgz#04ea6632d7ca600a2ae528f6f140ef0af9c01434" + integrity sha512-jmeLWEO6AA6g7HErhI3MXVGaMZtqDZjDwcHCg24WY954wO38Xn0zJ53VfpFc44ZTJLV9Ejd7ci9fLlG/HmJCeg== dependencies: "@babel/runtime" "^7.17.0" - "@mui/base" "5.0.0-alpha.68" - "@mui/system" "^5.4.1" - "@mui/types" "^7.1.1" - "@mui/utils" "^5.4.1" + "@mui/base" "5.0.0-alpha.69" + "@mui/system" "^5.4.2" + "@mui/types" "^7.1.2" + "@mui/utils" "^5.4.2" "@types/react-transition-group" "^4.4.4" clsx "^1.1.1" csstype "^3.0.10" @@ -2478,34 +2478,34 @@ react-is "^17.0.2" react-transition-group "^4.4.2" -"@mui/private-theming@^5.4.1": - version "5.4.1" - resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.4.1.tgz#5fa6490f35e78781239f1944ae80a7006c5a7648" - integrity sha512-Xbc4MXFZxv0A3hoc4TSDBhzjhstppKfc+gQcTMqqBZQP7KjnmxF+wO7rEPQuYRBihjCqQBdrHIGMLsKWrhkZkQ== +"@mui/private-theming@^5.4.2": + version "5.4.2" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.4.2.tgz#f0a05f908456a2f7b87ccb6fc3b6e1faae9d89e6" + integrity sha512-mlPDYYko4wIcwXjCPEmOWbNTT4DZ6h9YHdnRtQPnWM28+TRUHEo7SbydnnmVDQLRXUfaH4Y6XtEHIfBNPE/SLg== dependencies: "@babel/runtime" "^7.17.0" - "@mui/utils" "^5.4.1" + "@mui/utils" "^5.4.2" prop-types "^15.7.2" -"@mui/styled-engine@^5.4.1": - version "5.4.1" - resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.4.1.tgz#1427738e71c087f7005547e17d4a59de75597850" - integrity sha512-CFLNJkopRoAuShkgUZOTBVxdTlKu4w6L4kOwPi4r3QB2XXS6O5kyLHSsg9huUbtOYk5Dv5UZyUSc5pw4J7ezdg== +"@mui/styled-engine@^5.4.2": + version "5.4.2" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.4.2.tgz#e04903e06bd49fd10072a44ff38e13f5481bb64d" + integrity sha512-tz9p3aRtzXHKAg7x3BgP0hVQEoGKaxNCFxsJ+d/iqEHYvywWFSs6oxqYAvDHIRpvMlUZyPNoTrkcNnbdMmH/ng== dependencies: "@babel/runtime" "^7.17.0" "@emotion/cache" "^11.7.1" prop-types "^15.7.2" -"@mui/styles@^5.4.1": - version "5.4.1" - resolved "https://registry.yarnpkg.com/@mui/styles/-/styles-5.4.1.tgz#994171da902267184fffa19896ee5bbb07d4d783" - integrity sha512-ekw2NBC06re0H9SvCA1XgtFcghB8AQdGPXD3mjIz5ik+X+LvR+f2TeoCpJpkKp7UQdcNn6uuYi6BO6irTiQhdw== +"@mui/styles@^5.4.2": + version "5.4.2" + resolved "https://registry.yarnpkg.com/@mui/styles/-/styles-5.4.2.tgz#e0dadfc5de8255605f23c2f909f3669f0911bb88" + integrity sha512-BX75fNHmRF51yove9dBkH28gpSFjClOPDEnUwLTghPYN913OsqViS/iuCd61dxzygtEEmmeYuWfQjxu/F6vF5g== dependencies: "@babel/runtime" "^7.17.0" "@emotion/hash" "^0.8.0" - "@mui/private-theming" "^5.4.1" - "@mui/types" "^7.1.1" - "@mui/utils" "^5.4.1" + "@mui/private-theming" "^5.4.2" + "@mui/types" "^7.1.2" + "@mui/utils" "^5.4.2" clsx "^1.1.1" csstype "^3.0.10" hoist-non-react-statics "^3.3.2" @@ -2519,29 +2519,29 @@ jss-plugin-vendor-prefixer "^10.8.2" prop-types "^15.7.2" -"@mui/system@^5.4.1": - version "5.4.1" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.4.1.tgz#cf253369fbf1d960c792f0ec068fa28af81be3d4" - integrity sha512-07JBYf9iQdxIHZU8cFOLoxBnkQDUPLb7UBhNxo4998yEqpWFJ00WKgEVYBKvPl0X+MRU/20wqFz6yGIuCx4AeA== +"@mui/system@^5.4.2": + version "5.4.2" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.4.2.tgz#8166e406ba4628950bd79cec8159de25d5aef162" + integrity sha512-QegBVu6fxUNov1X9bWc1MZUTeV3A5g9PIpli7d0kzkGfq6JzrJWuPlhSPZ+6hlWmWky+bbAXhU65Qz8atWxDGw== dependencies: "@babel/runtime" "^7.17.0" - "@mui/private-theming" "^5.4.1" - "@mui/styled-engine" "^5.4.1" - "@mui/types" "^7.1.1" - "@mui/utils" "^5.4.1" + "@mui/private-theming" "^5.4.2" + "@mui/styled-engine" "^5.4.2" + "@mui/types" "^7.1.2" + "@mui/utils" "^5.4.2" clsx "^1.1.1" csstype "^3.0.10" prop-types "^15.7.2" -"@mui/types@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.1.1.tgz#9cf159dc60a101ee336e6ec74193a4f5f97f6160" - integrity sha512-33hbHFLCwenTpS+T4m4Cz7cQ/ng5g+IgtINkw1uDBVvi1oM83VNt/IGzWIQNPK8H2pr0WIfkmboD501bVdYsPw== +"@mui/types@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.1.2.tgz#4f3678ae77a7a3efab73b6e040469cc6df2144ac" + integrity sha512-SD7O1nVzqG+ckQpFjDhXPZjRceB8HQFHEvdLLrPhlJy4lLbwEBbxK74Tj4t6Jgk0fTvLJisuwOutrtYe9P/xBQ== -"@mui/utils@^5.4.1": - version "5.4.1" - resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.4.1.tgz#feb365ce9a4426587510f0943fd6d6e1889e06e6" - integrity sha512-5HzM+ZjlQqbSp7UTOvLlhAjkWB+o9Z4NzO0W+yhZ1KnxITr+zr/MBzYmmQ3kyvhui8pyhgRDoTcVgwb+02ZUZA== +"@mui/utils@^5.4.2": + version "5.4.2" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.4.2.tgz#3edda8f80de235418fff0424ee66e2a49793ec01" + integrity sha512-646dBCC57MXTo/Gf3AnZSHRHznaTETQq5x7AWp5FRQ4jPeyT4WSs18cpJVwkV01cAHKh06pNQTIufIALIWCL5g== dependencies: "@babel/runtime" "^7.17.0" "@types/prop-types" "^15.7.4" From e441d4f8ea0a3061cf34f0099270cb32f721d0c0 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Fri, 18 Feb 2022 14:49:26 +1100 Subject: [PATCH 12/18] fix rowy app setup for demo --- src/App.tsx | 2 +- src/components/Setup/BasicSetup/StepRules.tsx | 2 +- .../Setup/BasicSetup/StepStorageRules.tsx | 5 +- .../Setup/RowyAppSetup/StepOauth.tsx | 68 ++++++-------- .../Setup/RowyAppSetup/StepProject.tsx | 90 +++++++++---------- .../Setup/RowyAppSetup/StepRules.tsx | 84 ++++++++--------- .../Setup/RowyAppSetup/StepStorageRules.tsx | 63 +++++++++++++ src/components/Setup/SignInWithGoogle.tsx | 12 ++- src/pages/Setup/RowyAppSetup.tsx | 2 +- 9 files changed, 192 insertions(+), 136 deletions(-) create mode 100644 src/components/Setup/RowyAppSetup/StepStorageRules.tsx diff --git a/src/App.tsx b/src/App.tsx index 0c5b9683..eb3f25f1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -52,7 +52,7 @@ const UserSettingsPage = lazy(() => import("./pages/Settings/UserSettings" /* we // prettier-ignore const UserManagementPage = lazy(() => import("./pages/Settings/UserManagement" /* webpackChunkName: "UserManagementPage" */)); // prettier-ignore -const BasicSetupPage = lazy(() => import("@src/pages/Setup/BasicSetup" /* webpackChunkName: "BasicSetupPage" */)); +const BasicSetupPage = lazy(() => import("@src/pages/Setup/RowyAppSetup" /* webpackChunkName: "BasicSetupPage" */)); export default function App() { return ( diff --git a/src/components/Setup/BasicSetup/StepRules.tsx b/src/components/Setup/BasicSetup/StepRules.tsx index 5e5fc49a..8a70af32 100644 --- a/src/components/Setup/BasicSetup/StepRules.tsx +++ b/src/components/Setup/BasicSetup/StepRules.tsx @@ -28,7 +28,7 @@ import { export default { id: "rules", shortTitle: "Firestore Rules", - title: "Set up Firestore Rules", + title: "Set up Firestore rules", description: ( <> Rowy configuration is stored in the {CONFIG} collection on diff --git a/src/components/Setup/BasicSetup/StepStorageRules.tsx b/src/components/Setup/BasicSetup/StepStorageRules.tsx index 655a4256..f1a91675 100644 --- a/src/components/Setup/BasicSetup/StepStorageRules.tsx +++ b/src/components/Setup/BasicSetup/StepStorageRules.tsx @@ -17,8 +17,8 @@ import { export default { id: "storageRules", - shortTitle: "Storage Rules", - title: "Set up Firebase Storage Rules", + shortTitle: "Storage rules", + title: "Set up Firebase Storage rules", description: "Image and File fields store files in Firebase Storage. Your users will need read and write access.", body: StepStorageRules, @@ -96,7 +96,6 @@ function StepStorageRules({ isComplete, setComplete }: ISetupStepBodyProps) { color="primary" startIcon={} onClick={() => setComplete()} - sx={{ mt: -0.5 }} > Mark as done diff --git a/src/components/Setup/RowyAppSetup/StepOauth.tsx b/src/components/Setup/RowyAppSetup/StepOauth.tsx index b7029cfb..df2243fe 100644 --- a/src/components/Setup/RowyAppSetup/StepOauth.tsx +++ b/src/components/Setup/RowyAppSetup/StepOauth.tsx @@ -1,58 +1,48 @@ +import { useEffect } from "react"; import type { ISetupStep, ISetupStepBodyProps } from "../types"; -import { Typography, Link, Button } from "@mui/material"; +import { Link } from "@mui/material"; import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; import SetupItem from "../SetupItem"; +import SignInWithGoogle from "../SignInWithGoogle"; import { WIKI_LINKS } from "@src/constants/externalLinks"; +import { useAppContext } from "@src/contexts/AppContext"; export default { id: "oauth", shortTitle: "Access", title: "Allow Firebase access", + description: ( + <> + Allow Rowy to manage your Firebase Authentication, Firestore database, and + Firebase Storage. +
+
+ Your data and code always stays on your Firebase project.{" "} + + Learn more + + + + ), body: StepOauth, } as ISetupStep; function StepOauth({ isComplete, setComplete }: ISetupStepBodyProps) { - return ( - <> -
- - Allow Rowy to manage your Firebase Authentication, Firestore database, - and Firebase Storage. - - - Your data and code always stays on your Firebase project.{" "} - - Learn more - - - -
+ const { currentUser } = useAppContext(); - - - - + useEffect(() => { + if (currentUser && !isComplete) setComplete(); + }, [currentUser, isComplete, setComplete]); + + return ( + + + ); } diff --git a/src/components/Setup/RowyAppSetup/StepProject.tsx b/src/components/Setup/RowyAppSetup/StepProject.tsx index 1a886ab9..3c72f6ec 100644 --- a/src/components/Setup/RowyAppSetup/StepProject.tsx +++ b/src/components/Setup/RowyAppSetup/StepProject.tsx @@ -17,6 +17,7 @@ export default { id: "project", shortTitle: "Project", title: "Select project", + description: "Select which Firebase project to set up Rowy on.", body: StepProject, } as ISetupStep; @@ -24,57 +25,50 @@ function StepProject({ isComplete, setComplete }: ISetupStepBodyProps) { const isMobile = useMediaQuery((theme: any) => theme.breakpoints.down("md")); return ( - <> - - Select which Firebase project to set up Rowy on. - - - + - + v || ( + Select a project… + ), + }} + onChange={() => setComplete()} > - - v || ( - - Select a project… - - ), - }} - > - lorem - ipsum - dolor - sit - amet - + rowyio + rowy-service + tryrowy + rowy-cms-test + rowy-run + - - OR - + + OR + - - - - + +
+
); } diff --git a/src/components/Setup/RowyAppSetup/StepRules.tsx b/src/components/Setup/RowyAppSetup/StepRules.tsx index ad7e169c..d82ddfee 100644 --- a/src/components/Setup/RowyAppSetup/StepRules.tsx +++ b/src/components/Setup/RowyAppSetup/StepRules.tsx @@ -33,8 +33,15 @@ import { runRoutes } from "@src/constants/runRoutes"; export default { id: "rules", - shortTitle: "Firestore Rules", - title: "Set up Firestore Rules", + shortTitle: "Firestore rules", + title: "Set up Firestore rules", + description: ( + <> + Rowy configuration is stored in the {CONFIG} collection on + Firestore. Your users will need read access to this collection and admins + will need write access. + + ), body: StepRules, } as ISetupStep; @@ -60,11 +67,7 @@ function StepRules({ const [adminRule, setAdminRule] = useState(true); const [showManualMode, setShowManualMode] = useState(false); - const rules = `${error === "security-rules/not-found" ? RULES_START : ""}${ - adminRule ? ADMIN_RULES : "" - }${REQUIRED_RULES}${RULES_UTILS}${ - error === "security-rules/not-found" ? RULES_END : "" - }`.replace("\n", ""); + const rules = (adminRule ? ADMIN_RULES : "") + REQUIRED_RULES + RULES_UTILS; const [currentRules, setCurrentRules] = useState(""); useEffect(() => { @@ -91,23 +94,27 @@ function StepRules({ const [newRules, setNewRules] = useState(""); useEffect(() => { - let rulesToInsert = rules; + if (!currentRules) { + setNewRules(RULES_START + rules + RULES_END); + } else { + let rulesToInsert = rules; - if (currentRules.indexOf("function isDocOwner") > -1) { - rulesToInsert = rulesToInsert.replace(/function isDocOwner[^}]*}/s, ""); + if (currentRules.indexOf("function isDocOwner") > -1) { + rulesToInsert = rulesToInsert.replace(/function isDocOwner[^}]*}/s, ""); + } + if (currentRules.indexOf("function hasAnyRole") > -1) { + rulesToInsert = rulesToInsert.replace(/function hasAnyRole[^}]*}/s, ""); + } + + let inserted = currentRules.replace( + /match\s*\/databases\/\{database\}\/documents\s*\{/, + `match /databases/{database}/documents {\n` + rulesToInsert + ); + + if (hasInsecureRule) inserted = inserted.replace(insecureRuleRegExp, ""); + + setNewRules(inserted); } - if (currentRules.indexOf("function hasAnyRole") > -1) { - rulesToInsert = rulesToInsert.replace(/function hasAnyRole[^}]*}/s, ""); - } - - let inserted = currentRules.replace( - /match\s*\/databases\/\{database\}\/documents\s*\{/, - `match /databases/{database}/documents {\n` + rulesToInsert - ); - - if (hasInsecureRule) inserted = inserted.replace(insecureRuleRegExp, ""); - - setNewRules(inserted); }, [currentRules, rules, hasInsecureRule]); const [rulesStatus, setRulesStatus] = useState<"LOADING" | string>(""); @@ -168,12 +175,6 @@ function StepRules({ return ( <> - - Rowy configuration is stored in the {CONFIG} collection on - Firestore. Your users will need read access to this collection and - admins will need write access. - - {!hasRules && error !== "security-rules/not-found" && ( - - - We removed an insecure rule that allows anyone to access any part of - your database - + {hasInsecureRule && ( + + + We removed an insecure rule that allows anyone to access any part + of your database + + )} - Please verify the new rules first. + Please verify the new rules are valid first. setComplete()} loading={rulesStatus === "LOADING"} style={{ position: "sticky", bottom: 8 }} > - Set Firestore Rules + Set Firestore rules {rulesStatus !== "LOADING" && typeof rulesStatus === "string" && ( @@ -332,7 +336,7 @@ function StepRules({ )} {hasRules && ( - + )} ); diff --git a/src/components/Setup/RowyAppSetup/StepStorageRules.tsx b/src/components/Setup/RowyAppSetup/StepStorageRules.tsx new file mode 100644 index 00000000..a8ab2170 --- /dev/null +++ b/src/components/Setup/RowyAppSetup/StepStorageRules.tsx @@ -0,0 +1,63 @@ +import { useSnackbar } from "notistack"; +import type { ISetupStep, ISetupStepBodyProps } from "../types"; + +import { Typography, Button, Grid } from "@mui/material"; +import CopyIcon from "@src/assets/icons/Copy"; +import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; +import DoneIcon from "@mui/icons-material/Done"; + +import SetupItem from "../SetupItem"; +import DiffEditor from "@src/components/CodeEditor/DiffEditor"; + +import { useAppContext } from "@src/contexts/AppContext"; +import { + RULES_START, + RULES_END, + REQUIRED_RULES, +} from "@src/config/storageRules"; + +export default { + id: "storageRules", + shortTitle: "Storage rules", + title: "Set up Firebase Storage rules", + description: + "Image and File fields store files in Firebase Storage. Your users will need read and write access.", + body: StepStorageRules, +} as ISetupStep; + +const rules = RULES_START + REQUIRED_RULES + RULES_END; + +function StepStorageRules({ isComplete, setComplete }: ISetupStepBodyProps) { + const { projectId } = useAppContext(); + const { enqueueSnackbar } = useSnackbar(); + + return ( + <> + + + + + Please verify the new rules are valid first. + + + + + + ); +} diff --git a/src/components/Setup/SignInWithGoogle.tsx b/src/components/Setup/SignInWithGoogle.tsx index 67e3f7d4..ff7369eb 100644 --- a/src/components/Setup/SignInWithGoogle.tsx +++ b/src/components/Setup/SignInWithGoogle.tsx @@ -41,16 +41,22 @@ export default function SignInWithGoogle({ Google logo } onClick={handleSignIn} loading={status === "LOADING"} + style={{ minHeight: 40 }} + sx={{ + minHeight: 40, + "& .MuiButton-startIcon": { mr: 3 }, + "&.MuiButton-outlined": { pr: 3 }, + }} {...props} > Sign in with Google diff --git a/src/pages/Setup/RowyAppSetup.tsx b/src/pages/Setup/RowyAppSetup.tsx index 07242d42..4f57aa16 100644 --- a/src/pages/Setup/RowyAppSetup.tsx +++ b/src/pages/Setup/RowyAppSetup.tsx @@ -5,7 +5,7 @@ import StepWelcome from "@src/components/Setup/RowyAppSetup/StepWelcome"; import StepOauth from "@src/components/Setup/RowyAppSetup/StepOauth"; import StepProject from "@src/components/Setup/RowyAppSetup/StepProject"; import StepRules from "@src/components/Setup/RowyAppSetup/StepRules"; -import StepStorageRules from "@src/components/Setup/BasicSetup/StepStorageRules"; +import StepStorageRules from "@src/components/Setup/RowyAppSetup/StepStorageRules"; import StepFinish from "@src/components/Setup/BasicSetup/StepFinish"; const steps = [ From 221eb933bd33b3ff615287afc75c500bb179e517 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Fri, 18 Feb 2022 16:35:40 +1100 Subject: [PATCH 13/18] fix SetupLayout styles --- src/components/Setup/SetupLayout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Setup/SetupLayout.tsx b/src/components/Setup/SetupLayout.tsx index b9c33b14..a26871d9 100644 --- a/src/components/Setup/SetupLayout.tsx +++ b/src/components/Setup/SetupLayout.tsx @@ -179,7 +179,7 @@ export default function SetupLayout({ )} {step.layout === "centered" ? ( - + Date: Sun, 27 Feb 2022 22:42:35 +0000 Subject: [PATCH 14/18] Bump url-parse from 1.4.7 to 1.5.10 Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.4.7 to 1.5.10. - [Release notes](https://github.com/unshiftio/url-parse/releases) - [Commits](https://github.com/unshiftio/url-parse/compare/1.4.7...1.5.10) --- updated-dependencies: - dependency-name: url-parse dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/yarn.lock b/yarn.lock index badf1454..47a669b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14052,9 +14052,9 @@ querystring@^0.2.0: integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== querystringify@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" - integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== queue-microtask@^1.2.2: version "1.2.3" @@ -17108,18 +17108,10 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-parse@^1.4.3: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - -url-parse@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b" - integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q== +url-parse@^1.4.3, url-parse@^1.5.1: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" From a842fb37456632d40a349a7f6857f280ba7ab756 Mon Sep 17 00:00:00 2001 From: Nikita Sviridenko Date: Mon, 28 Feb 2022 11:48:37 +0100 Subject: [PATCH 15/18] Fix initial values for nested fields --- src/components/SideDrawer/Form/index.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/SideDrawer/Form/index.tsx b/src/components/SideDrawer/Form/index.tsx index 6895fc2b..2236bd7f 100644 --- a/src/components/SideDrawer/Form/index.tsx +++ b/src/components/SideDrawer/Form/index.tsx @@ -2,6 +2,7 @@ import { createElement, useEffect } from "react"; import { useForm } from "react-hook-form"; import _sortBy from "lodash/sortBy"; import _isEmpty from "lodash/isEmpty"; +import _set from "lodash/set"; import createPersistedState from "use-persisted-state"; import { Stack, FormControlLabel, Switch } from "@mui/material"; @@ -44,7 +45,16 @@ export default function Form({ values }: IFormProps) { // Get initial values from fields config. This won’t be written to the db // when the SideDrawer is opened. Only dirty fields will be written const initialValues = fields.reduce( - (a, { key, type }) => ({ ...a, [key]: getFieldProp("initialValue", type) }), + (a, { key, type }) => { + const initialValue = getFieldProp("initialValue", type); + const nextValues = { ...a }; + if (key.indexOf('.') !== -1) { + _set(nextValues, key, initialValue); + } else { + nextValues[key] = initialValue; + } + return nextValues; + }, {} ); const { ref: docRef, ...rowValues } = values; From 72d82549bfd80fc23cea19b2815979039cad1e86 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Wed, 2 Mar 2022 18:54:30 +1100 Subject: [PATCH 16/18] bump version number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8dae7833..fb7fd252 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Rowy", - "version": "2.3.2", + "version": "2.4.0-rc.0", "homepage": "https://rowy.io", "repository": { "type": "git", From 927cfa72df0b7d15d41225cb73bf7887ba328521 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Thu, 3 Mar 2022 17:40:15 +1100 Subject: [PATCH 17/18] remove unused setup flow --- src/App.tsx | 4 +- .../Setup/RowyAppSetup/StepOauth.tsx | 48 -- .../Setup/RowyAppSetup/StepProject.tsx | 74 ---- .../Setup/RowyAppSetup/StepRules.tsx | 375 ---------------- .../Setup/RowyAppSetup/StepStorageRules.tsx | 63 --- .../Setup/RowyAppSetup/StepWelcome.tsx | 91 ---- .../Setup/RowyRunSetup/StepRowyRun.tsx | 217 --------- .../{BasicSetup => Steps}/StepFinish.tsx | 0 .../Setup/{BasicSetup => Steps}/StepRules.tsx | 0 .../StepStorageRules.tsx | 0 .../{BasicSetup => Steps}/StepWelcome.tsx | 0 src/pages/{Setup/BasicSetup.tsx => Setup.tsx} | 10 +- src/pages/Setup/RowyAppSetup.tsx | 34 -- src/pages/Setup/Setup.tsx.old | 410 ------------------ 14 files changed, 7 insertions(+), 1319 deletions(-) delete mode 100644 src/components/Setup/RowyAppSetup/StepOauth.tsx delete mode 100644 src/components/Setup/RowyAppSetup/StepProject.tsx delete mode 100644 src/components/Setup/RowyAppSetup/StepRules.tsx delete mode 100644 src/components/Setup/RowyAppSetup/StepStorageRules.tsx delete mode 100644 src/components/Setup/RowyAppSetup/StepWelcome.tsx delete mode 100644 src/components/Setup/RowyRunSetup/StepRowyRun.tsx rename src/components/Setup/{BasicSetup => Steps}/StepFinish.tsx (100%) rename src/components/Setup/{BasicSetup => Steps}/StepRules.tsx (100%) rename src/components/Setup/{BasicSetup => Steps}/StepStorageRules.tsx (100%) rename src/components/Setup/{BasicSetup => Steps}/StepWelcome.tsx (100%) rename src/pages/{Setup/BasicSetup.tsx => Setup.tsx} (56%) delete mode 100644 src/pages/Setup/RowyAppSetup.tsx delete mode 100644 src/pages/Setup/Setup.tsx.old diff --git a/src/App.tsx b/src/App.tsx index eb3f25f1..5c1aec54 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -52,7 +52,7 @@ const UserSettingsPage = lazy(() => import("./pages/Settings/UserSettings" /* we // prettier-ignore const UserManagementPage = lazy(() => import("./pages/Settings/UserManagement" /* webpackChunkName: "UserManagementPage" */)); // prettier-ignore -const BasicSetupPage = lazy(() => import("@src/pages/Setup/RowyAppSetup" /* webpackChunkName: "BasicSetupPage" */)); +const SetupPage = lazy(() => import("@src/pages/Setup" /* webpackChunkName: "SetupPage" */)); export default function App() { return ( @@ -97,7 +97,7 @@ export default function App() { } + render={() => } /> - Allow Rowy to manage your Firebase Authentication, Firestore database, and - Firebase Storage. -
-
- Your data and code always stays on your Firebase project.{" "} - - Learn more - - - - ), - body: StepOauth, -} as ISetupStep; - -function StepOauth({ isComplete, setComplete }: ISetupStepBodyProps) { - const { currentUser } = useAppContext(); - - useEffect(() => { - if (currentUser && !isComplete) setComplete(); - }, [currentUser, isComplete, setComplete]); - - return ( - - - - ); -} diff --git a/src/components/Setup/RowyAppSetup/StepProject.tsx b/src/components/Setup/RowyAppSetup/StepProject.tsx deleted file mode 100644 index 3c72f6ec..00000000 --- a/src/components/Setup/RowyAppSetup/StepProject.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { ISetupStep, ISetupStepBodyProps } from "../types"; - -import { - useMediaQuery, - Typography, - Stack, - TextField, - MenuItem, - Divider, - Button, -} from "@mui/material"; -import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; - -import SetupItem from "../SetupItem"; - -export default { - id: "project", - shortTitle: "Project", - title: "Select project", - description: "Select which Firebase project to set up Rowy on.", - body: StepProject, -} as ISetupStep; - -function StepProject({ isComplete, setComplete }: ISetupStepBodyProps) { - const isMobile = useMediaQuery((theme: any) => theme.breakpoints.down("md")); - - return ( - - - - v || ( - Select a project… - ), - }} - onChange={() => setComplete()} - > - rowyio - rowy-service - tryrowy - rowy-cms-test - rowy-run - - - - OR - - - - - - ); -} diff --git a/src/components/Setup/RowyAppSetup/StepRules.tsx b/src/components/Setup/RowyAppSetup/StepRules.tsx deleted file mode 100644 index d82ddfee..00000000 --- a/src/components/Setup/RowyAppSetup/StepRules.tsx +++ /dev/null @@ -1,375 +0,0 @@ -import { useState, useEffect } from "react"; -import type { ISetupStep, ISetupStepBodyProps } from "../types"; - -import { - Typography, - FormControlLabel, - Checkbox, - Button, - Link, - Grid, -} from "@mui/material"; -import LoadingButton from "@mui/lab/LoadingButton"; -import InfoIcon from "@mui/icons-material/InfoOutlined"; -import CopyIcon from "@src/assets/icons/Copy"; -import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; - -import SetupItem from "../SetupItem"; -import DiffEditor from "@src/components/CodeEditor/DiffEditor"; - -import { useAppContext } from "@src/contexts/AppContext"; -import { CONFIG } from "@src/config/dbPaths"; -import { - RULES_START, - RULES_END, - REQUIRED_RULES, - ADMIN_RULES, - RULES_UTILS, - INSECURE_RULES, -} from "@src/config/firestoreRules"; -import { rowyRun } from "@src/utils/rowyRun"; -import { runRoutes } from "@src/constants/runRoutes"; -// import { useConfirmation } from "@src/components/ConfirmationDialog"; - -export default { - id: "rules", - shortTitle: "Firestore rules", - title: "Set up Firestore rules", - description: ( - <> - Rowy configuration is stored in the {CONFIG} collection on - Firestore. Your users will need read access to this collection and admins - will need write access. - - ), - body: StepRules, -} as ISetupStep; - -const insecureRuleRegExp = new RegExp( - INSECURE_RULES.replace(/\//g, "\\/") - .replace(/\*/g, "\\*") - .replace(/\s{2,}/g, "\\s+") - .replace(/\s/g, "\\s*") - .replace(/\n/g, "\\s+") - .replace(/;/g, ";?") -); - -function StepRules({ - rowyRunUrl, - isComplete, - setComplete, -}: ISetupStepBodyProps & { rowyRunUrl: string }) { - const { projectId, getAuthToken } = useAppContext(); - // const { requestConfirmation } = useConfirmation(); - - const [error, setError] = useState(false); - const [hasRules, setHasRules] = useState(isComplete); - const [adminRule, setAdminRule] = useState(true); - const [showManualMode, setShowManualMode] = useState(false); - - const rules = (adminRule ? ADMIN_RULES : "") + REQUIRED_RULES + RULES_UTILS; - - const [currentRules, setCurrentRules] = useState(""); - useEffect(() => { - if (rowyRunUrl && !hasRules && !currentRules) - getAuthToken(true) - .then((authToken) => - rowyRun({ - serviceUrl: rowyRunUrl, - route: runRoutes.firestoreRules, - authToken, - }) - ) - .then((data) => { - if (data?.code) { - setError(data.code); - setShowManualMode(true); - } else { - setCurrentRules(data?.source?.[0]?.content ?? ""); - } - }); - }, [rowyRunUrl, hasRules, currentRules, getAuthToken]); - - const hasInsecureRule = insecureRuleRegExp.test(currentRules); - - const [newRules, setNewRules] = useState(""); - useEffect(() => { - if (!currentRules) { - setNewRules(RULES_START + rules + RULES_END); - } else { - let rulesToInsert = rules; - - if (currentRules.indexOf("function isDocOwner") > -1) { - rulesToInsert = rulesToInsert.replace(/function isDocOwner[^}]*}/s, ""); - } - if (currentRules.indexOf("function hasAnyRole") > -1) { - rulesToInsert = rulesToInsert.replace(/function hasAnyRole[^}]*}/s, ""); - } - - let inserted = currentRules.replace( - /match\s*\/databases\/\{database\}\/documents\s*\{/, - `match /databases/{database}/documents {\n` + rulesToInsert - ); - - if (hasInsecureRule) inserted = inserted.replace(insecureRuleRegExp, ""); - - setNewRules(inserted); - } - }, [currentRules, rules, hasInsecureRule]); - - const [rulesStatus, setRulesStatus] = useState<"LOADING" | string>(""); - const setRules = async () => { - setRulesStatus("LOADING"); - try { - const authToken = await getAuthToken(); - if (!authToken) throw new Error("Failed to generate auth token"); - - const res = await rowyRun({ - serviceUrl: rowyRunUrl, - route: runRoutes.setFirestoreRules, - authToken, - body: { ruleset: newRules }, - }); - if (!res.success) throw new Error(res.message); - const isSuccessful = await checkRules(rowyRunUrl, authToken); - if (isSuccessful) { - setComplete(); - setHasRules(true); - } - setRulesStatus(""); - } catch (e: any) { - console.error(e); - setRulesStatus(e.message); - } - }; - const verifyRules = async () => { - setRulesStatus("LOADING"); - try { - const authToken = await getAuthToken(); - if (!authToken) throw new Error("Failed to generate auth token"); - - const isSuccessful = await checkRules(rowyRunUrl, authToken); - if (isSuccessful) { - setComplete(); - setHasRules(true); - } - setRulesStatus(""); - } catch (e: any) { - console.error(e); - setRulesStatus(e.message); - } - }; - - // const handleSkip = () => { - // requestConfirmation({ - // title: "Skip rules", - // body: "This might prevent you or other users in your project from accessing firestore data on Rowy", - // confirm: "Skip", - // cancel: "cancel", - // handleConfirm: async () => { - // setComplete(); - // setHasRules(true); - // }, - // }); - // }; - - return ( - <> - {!hasRules && error !== "security-rules/not-found" && ( - - setAdminRule(e.target.checked)} - /> - } - label="Allow admins to read and write all documents" - sx={{ "&&": { ml: -11 / 8, mb: -11 / 8 }, width: "100%" }} - /> - - {hasInsecureRule && ( - - - We removed an insecure rule that allows anyone to access any part - of your database - - )} - - - - - Please verify the new rules are valid first. - - - setComplete()} - loading={rulesStatus === "LOADING"} - style={{ position: "sticky", bottom: 8 }} - > - Set Firestore rules - - {rulesStatus !== "LOADING" && typeof rulesStatus === "string" && ( - - {rulesStatus} - - )} - {!showManualMode && ( - setShowManualMode(true)} - > - Alternatively, add these rules in the Firebase Console - - )} - - )} - - {!hasRules && showManualMode && ( - <> - - $1` - ), - }} - /> - -
- - - - - - - - - - - {rulesStatus !== "LOADING" && - typeof rulesStatus === "string" && ( - - {rulesStatus} - - )} - - -
-
- - - Verify - - } - > - {rulesStatus !== "LOADING" && typeof rulesStatus === "string" && ( - - {rulesStatus} - - )} - - - )} - - {hasRules && ( - - )} - - ); -} - -export const checkRules = async ( - rowyRunUrl: string, - authToken: string, - signal?: AbortSignal -) => { - if (!authToken) return false; - - try { - const res = await rowyRun({ - serviceUrl: rowyRunUrl, - route: runRoutes.firestoreRules, - authToken, - signal, - }); - const rules = res?.source?.[0]?.content || ""; - if (!rules) return false; - - const sanitizedRules = rules.replace(/\s{2,}/g, " ").replace(/\n/g, " "); - const hasRules = - sanitizedRules.includes( - REQUIRED_RULES.replace(/\s{2,}/g, " ").replace(/\n/g, " ") - ) && - sanitizedRules.includes( - RULES_UTILS.replace(/\s{2,}/g, " ").replace(/\n/g, " ") - ); - return hasRules; - } catch (e: any) { - console.error(e); - return false; - } -}; diff --git a/src/components/Setup/RowyAppSetup/StepStorageRules.tsx b/src/components/Setup/RowyAppSetup/StepStorageRules.tsx deleted file mode 100644 index a8ab2170..00000000 --- a/src/components/Setup/RowyAppSetup/StepStorageRules.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useSnackbar } from "notistack"; -import type { ISetupStep, ISetupStepBodyProps } from "../types"; - -import { Typography, Button, Grid } from "@mui/material"; -import CopyIcon from "@src/assets/icons/Copy"; -import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; -import DoneIcon from "@mui/icons-material/Done"; - -import SetupItem from "../SetupItem"; -import DiffEditor from "@src/components/CodeEditor/DiffEditor"; - -import { useAppContext } from "@src/contexts/AppContext"; -import { - RULES_START, - RULES_END, - REQUIRED_RULES, -} from "@src/config/storageRules"; - -export default { - id: "storageRules", - shortTitle: "Storage rules", - title: "Set up Firebase Storage rules", - description: - "Image and File fields store files in Firebase Storage. Your users will need read and write access.", - body: StepStorageRules, -} as ISetupStep; - -const rules = RULES_START + REQUIRED_RULES + RULES_END; - -function StepStorageRules({ isComplete, setComplete }: ISetupStepBodyProps) { - const { projectId } = useAppContext(); - const { enqueueSnackbar } = useSnackbar(); - - return ( - <> - - - - - Please verify the new rules are valid first. - - - - - - ); -} diff --git a/src/components/Setup/RowyAppSetup/StepWelcome.tsx b/src/components/Setup/RowyAppSetup/StepWelcome.tsx deleted file mode 100644 index 55edced5..00000000 --- a/src/components/Setup/RowyAppSetup/StepWelcome.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import type { ISetupStep, ISetupStepBodyProps } from "../types"; - -import { - FormControlLabel, - Checkbox, - Typography, - Link, - Button, -} from "@mui/material"; - -import { EXTERNAL_LINKS } from "@src/constants/externalLinks"; - -export default { - id: "welcome", - layout: "centered", - shortTitle: "Welcome", - title: "Welcome", - description: ( - <> - Get started with Rowy in just a few minutes. -
-
- We have no access to your data and it always stays on your Firebase - project. - - ), - body: StepWelcome, -} as ISetupStep; - -function StepWelcome({ isComplete, setComplete }: ISetupStepBodyProps) { - return ( - <> - setComplete(e.target.checked)} - /> - } - label={ - <> - I agree to the{" "} - - Terms and Conditions - {" "} - and{" "} - - Privacy Policy - - - } - sx={{ - pr: 1, - textAlign: "left", - alignItems: "flex-start", - p: 0, - m: 0, - }} - /> - - - -
- - Want to invite your teammate to help with setup instead? - - -
- - ); -} diff --git a/src/components/Setup/RowyRunSetup/StepRowyRun.tsx b/src/components/Setup/RowyRunSetup/StepRowyRun.tsx deleted file mode 100644 index 6c597017..00000000 --- a/src/components/Setup/RowyRunSetup/StepRowyRun.tsx +++ /dev/null @@ -1,217 +0,0 @@ -import { useState, useEffect } from "react"; -import { useLocation, useHistory } from "react-router-dom"; -import queryString from "query-string"; -import type { ISetupStep, ISetupStepBodyProps } from "../types"; - -import { Button, Typography, Stack, TextField } from "@mui/material"; -import LoadingButton from "@mui/lab/LoadingButton"; -import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; - -import SetupItem from "../SetupItem"; - -import { rowyRun } from "@src/utils/rowyRun"; -import { runRoutes } from "@src/constants/runRoutes"; -import { EXTERNAL_LINKS, WIKI_LINKS } from "@src/constants/externalLinks"; - -export default { - id: "rowyRun", - shortTitle: "Rowy Run", - title: "Set up Rowy Run", - description: - "Rowy Run is a Google Cloud Run instance that provides backend functionality, such as table action scripts, user management, and easy Cloud Function deployment.", - body: StepRowyRun, -} as ISetupStep; - -function StepRowyRun({ - isComplete, - setComplete, -}: // rowyRunUrl: paramsRowyRunUrl, -ISetupStepBodyProps) { - const { pathname } = useLocation(); - const history = useHistory(); - - const [isValidRowyRunUrl, setIsValidRowyRunUrl] = useState(isComplete); - const [isLatestVersion, setIsLatestVersion] = useState(isComplete); - - const [rowyRunUrl, setRowyRunUrl] = useState("paramsRowyRunUrl"); - const [latestVersion, setLatestVersion] = useState(""); - const [verificationStatus, setVerificationStatus] = useState< - "IDLE" | "LOADING" | "FAIL" - >("IDLE"); - - const verifyRowyRun = async () => { - setVerificationStatus("LOADING"); - - try { - const result = await checkRowyRun(rowyRunUrl); - setVerificationStatus("IDLE"); - - if (result.isValidRowyRunUrl) setIsValidRowyRunUrl(true); - - setLatestVersion(result.latestVersion); - - if (result.isLatestVersion) { - setIsLatestVersion(true); - setComplete(); - history.replace({ - pathname, - search: queryString.stringify({ rowyRunUrl }), - }); - } - } catch (e: any) { - console.error(`Failed to verify Rowy Run URL: ${e}`); - setVerificationStatus("FAIL"); - } - }; - - // useEffect(() => { - // if (!isValidRowyRunUrl && paramsRowyRunUrl) console.log(paramsRowyRunUrl); - // }, [paramsRowyRunUrl, isValidRowyRunUrl]); - - const deployButton = window.location.hostname.includes( - EXTERNAL_LINKS.rowyAppHostName - ) ? ( - - Run on Google Cloud - - ) : ( - - ); - - return ( - <> - - {!isValidRowyRunUrl && ( - <> - {deployButton} - -
- - Then paste the Rowy Run instance URL below: - - - - setRowyRunUrl(e.target.value)} - type="url" - autoComplete="url" - fullWidth - error={verificationStatus === "FAIL"} - helperText={ - verificationStatus === "FAIL" ? "Invalid URL" : " " - } - /> - - Verify - - -
- - )} -
- {isValidRowyRunUrl && ( - - {!isLatestVersion && ( - - {deployButton} - - Verify - - - )} - - )} - - ); -} - -export const checkRowyRun = async ( - serviceUrl: string, - signal?: AbortSignal -) => { - const result = { - isValidRowyRunUrl: false, - isLatestVersion: false, - latestVersion: "", - }; - - try { - const res = await rowyRun({ serviceUrl, route: runRoutes.version, signal }); - if (!res.version) return result; - - result.isValidRowyRunUrl = true; - - // https://docs.github.com/en/rest/reference/repos#get-the-latest-release - const endpoint = - EXTERNAL_LINKS.rowyRunGitHub.replace( - "github.com", - "api.github.com/repos" - ) + "/releases/latest"; - const latestVersionReq = await fetch(endpoint, { - headers: { Accept: "application/vnd.github.v3+json" }, - signal, - }); - const latestVersion = await latestVersionReq.json(); - if (!latestVersion.tag_name) return result; - - if (latestVersion.tag_name > "v" + res.version) { - result.isLatestVersion = false; - result.latestVersion = latestVersion.tag_name; - } else { - result.isLatestVersion = true; - result.latestVersion = res.version; - } - } catch (e: any) { - console.error(e); - } finally { - return result; - } -}; diff --git a/src/components/Setup/BasicSetup/StepFinish.tsx b/src/components/Setup/Steps/StepFinish.tsx similarity index 100% rename from src/components/Setup/BasicSetup/StepFinish.tsx rename to src/components/Setup/Steps/StepFinish.tsx diff --git a/src/components/Setup/BasicSetup/StepRules.tsx b/src/components/Setup/Steps/StepRules.tsx similarity index 100% rename from src/components/Setup/BasicSetup/StepRules.tsx rename to src/components/Setup/Steps/StepRules.tsx diff --git a/src/components/Setup/BasicSetup/StepStorageRules.tsx b/src/components/Setup/Steps/StepStorageRules.tsx similarity index 100% rename from src/components/Setup/BasicSetup/StepStorageRules.tsx rename to src/components/Setup/Steps/StepStorageRules.tsx diff --git a/src/components/Setup/BasicSetup/StepWelcome.tsx b/src/components/Setup/Steps/StepWelcome.tsx similarity index 100% rename from src/components/Setup/BasicSetup/StepWelcome.tsx rename to src/components/Setup/Steps/StepWelcome.tsx diff --git a/src/pages/Setup/BasicSetup.tsx b/src/pages/Setup.tsx similarity index 56% rename from src/pages/Setup/BasicSetup.tsx rename to src/pages/Setup.tsx index 7c59d967..d8401efc 100644 --- a/src/pages/Setup/BasicSetup.tsx +++ b/src/pages/Setup.tsx @@ -1,14 +1,14 @@ import { useState } from "react"; import SetupLayout from "@src/components/Setup/SetupLayout"; -import StepWelcome from "@src/components/Setup/BasicSetup/StepWelcome"; -import StepRules from "@src/components/Setup/BasicSetup/StepRules"; -import StepStorageRules from "@src/components/Setup/BasicSetup/StepStorageRules"; -import StepFinish from "@src/components/Setup/BasicSetup/StepFinish"; +import StepWelcome from "@src/components/Setup/Steps/StepWelcome"; +import StepRules from "@src/components/Setup/Steps/StepRules"; +import StepStorageRules from "@src/components/Setup/Steps/StepStorageRules"; +import StepFinish from "@src/components/Setup/Steps/StepFinish"; const steps = [StepWelcome, StepRules, StepStorageRules, StepFinish]; -export default function BasicSetupPage() { +export default function SetupPage() { const [completion, setCompletion] = useState>({ welcome: false, rules: false, diff --git a/src/pages/Setup/RowyAppSetup.tsx b/src/pages/Setup/RowyAppSetup.tsx deleted file mode 100644 index 4f57aa16..00000000 --- a/src/pages/Setup/RowyAppSetup.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useState } from "react"; - -import SetupLayout from "@src/components/Setup/SetupLayout"; -import StepWelcome from "@src/components/Setup/RowyAppSetup/StepWelcome"; -import StepOauth from "@src/components/Setup/RowyAppSetup/StepOauth"; -import StepProject from "@src/components/Setup/RowyAppSetup/StepProject"; -import StepRules from "@src/components/Setup/RowyAppSetup/StepRules"; -import StepStorageRules from "@src/components/Setup/RowyAppSetup/StepStorageRules"; -import StepFinish from "@src/components/Setup/BasicSetup/StepFinish"; - -const steps = [ - StepWelcome, - StepOauth, - StepProject, - StepRules, - StepStorageRules, - StepFinish, -]; - -export default function RowyAppSetupPage() { - const [completion, setCompletion] = useState>({ - welcome: false, - rules: false, - storageRules: false, - }); - - return ( - - ); -} diff --git a/src/pages/Setup/Setup.tsx.old b/src/pages/Setup/Setup.tsx.old deleted file mode 100644 index 77746075..00000000 --- a/src/pages/Setup/Setup.tsx.old +++ /dev/null @@ -1,410 +0,0 @@ -import { useState, useEffect } from "react"; -import { use100vh } from "react-div-100vh"; -import { SwitchTransition } from "react-transition-group"; -import { useLocation, Link } from "react-router-dom"; -import queryString from "query-string"; - -import { - useMediaQuery, - Paper, - Stepper, - Step, - StepButton, - MobileStepper, - IconButton, - Typography, - Stack, - DialogActions, - Button, - Tooltip, -} from "@mui/material"; -import { alpha } from "@mui/material/styles"; -import LoadingButton from "@mui/lab/LoadingButton"; -import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; -import ChevronRightIcon from "@mui/icons-material/ChevronRight"; - -import BrandedBackground, { Wrapper } from "@src/assets/BrandedBackground"; -import Logo from "@src/assets/Logo"; -import ScrollableDialogContent from "@src/components/Modal/ScrollableDialogContent"; -import { SlideTransition } from "@src/components/Modal/SlideTransition"; - -import StepWelcome from "@src/components/Setup/RowyAppSetup/StepWelcome"; -import Step1Oauth from "@src/components/Setup/Step1Oauth"; -// prettier-ignore -import Step2ProjectOwner, { checkProjectOwner } from "@src/components/Setup/Step2ProjectOwner"; -// import Step3Rules, { checkRules } from "@src/components/Setup/Step3Rules"; -import Step4Migrate, { checkMigrate } from "@src/components/Setup/Step4Migrate"; -import Step5Finish from "@src/components/Setup/Step6Finish"; - -import routes from "@src/constants/routes"; -import { useAppContext } from "@src/contexts/AppContext"; -import { analytics } from "analytics"; - -export interface ISetupStep { - id: string; - layout?: "centered"; - shortTitle: string; - title: React.ReactNode; - description?: React.ReactNode; - body: React.ReactNode; -} - -export interface ISetupStepBodyProps { - completion: Record; - setCompletion: React.Dispatch>>; - checkAllSteps: typeof checkAllSteps; - rowyRunUrl: string; -} - -const BASE_WIDTH = 1024; - -const checkAllSteps = async ( - rowyRunUrl: string, - currentUser: firebase.default.User | null | undefined, - userRoles: string[] | null, - authToken: string, - signal: AbortSignal -) => { - console.log("Check all steps"); - const completion: Record = {}; - - // const rowyRunValidation = await checkRowyRun(rowyRunUrl, signal); - // if (rowyRunValidation.isValidRowyRunUrl) { - // if (rowyRunValidation.isLatestVersion) completion.rowyRun = true; - - // const promises = [ - // checkProjectOwner(rowyRunUrl, currentUser, userRoles, signal).then( - // (projectOwner) => { - // if (projectOwner) completion.projectOwner = true; - // } - // ), - // checkRules(rowyRunUrl, authToken, signal).then((rules) => { - // if (rules) completion.rules = true; - // }), - // checkMigrate(rowyRunUrl, authToken, signal).then((requiresMigration) => { - // if (requiresMigration) completion.migrate = false; - // }), - // ]; - // await Promise.all(promises); - // } - - return completion; -}; - -export default function SetupPage() { - const { currentUser, userRoles, getAuthToken } = useAppContext(); - - const fullScreenHeight = use100vh() ?? 0; - const isMobile = useMediaQuery((theme: any) => theme.breakpoints.down("sm")); - - const { search } = useLocation(); - const params = queryString.parse(search); - const rowyRunUrl = decodeURIComponent((params.rowyRunUrl as string) || ""); - - const [stepId, setStepId] = useState("welcome"); - const [completion, setCompletion] = useState>({ - welcome: false, - rowyRun: false, - serviceAccount: false, - projectOwner: false, - rules: false, - }); - - // const [checkingAllSteps, setCheckingAllSteps] = useState(false); - // useEffect(() => { - // const controller = new AbortController(); - // const signal = controller.signal; - - // if (rowyRunUrl) { - // setCheckingAllSteps(true); - // getAuthToken().then((authToken) => - // checkAllSteps( - // rowyRunUrl, - // currentUser, - // userRoles, - // authToken, - // signal - // ).then((result) => { - // if (!signal.aborted) { - // setCompletion((c) => ({ ...c, ...result })); - // setCheckingAllSteps(false); - // } - // }) - // ); - // } - - // return () => controller.abort(); - // }, [rowyRunUrl, currentUser, userRoles, getAuthToken]); - - const stepProps = { completion, setCompletion, checkAllSteps, rowyRunUrl }; - - const handleContinue = () => { - let nextIncompleteStepIndex = stepIndex + 1; - while (completion[steps[nextIncompleteStepIndex]?.id]) { - console.log("iteration", steps[nextIncompleteStepIndex]?.id); - nextIncompleteStepIndex++; - } - - const nextStepId = steps[nextIncompleteStepIndex].id; - analytics.logEvent("setup_step", { step: nextStepId }); - setStepId(nextStepId); - }; - - const steps: ISetupStep[] = [ - { - id: "welcome", - layout: "centered" as "centered", - shortTitle: "Welcome", - title: `Welcome`, - body: , - }, - // { - // id: "rowyRun", - // shortTitle: `Rowy Run`, - // title: `Set up Rowy Run`, - // body: , - // }, - // { - // id: "projectOwner", - // shortTitle: `Project owner`, - // title: `Set up project owner`, - // body: , - // }, - { - id: "oauth", - shortTitle: `Access`, - title: `Allow Firebase access`, - body: , - }, - { - id: "project", - shortTitle: `Project`, - title: `Select project`, - body: , - }, - { - id: "rules", - shortTitle: `Firestore Rules`, - title: `Set up Firestore Rules`, - body: , - }, - { - id: "storageRules", - shortTitle: `Storage Rules`, - title: `Set up Firestore Rules`, - body: , - }, - { - id: "finish", - layout: "centered" as "centered", - shortTitle: `Finish`, - title: `You’re all set up!`, - body: , - // actions: ( - // - // ), - }, - ]; - - const step = - steps.find((step) => step.id === (stepId || steps[0].id)) ?? steps[0]; - const stepIndex = steps.findIndex( - (step) => step.id === (stepId || steps[0].id) - ); - const listedSteps = steps.filter((step) => step.layout !== "centered"); - - return ( - - - - alpha(theme.palette.background.paper, 0.75), - backdropFilter: "blur(20px) saturate(150%)", - - maxWidth: BASE_WIDTH, - width: (theme) => `calc(100vw - ${theme.spacing(2)})`, - height: (theme) => - `calc(${ - fullScreenHeight > 0 ? `${fullScreenHeight}px` : "100vh" - } - ${theme.spacing( - 2 - )} - env(safe-area-inset-top) - env(safe-area-inset-bottom))`, - resize: "both", - - p: 0, - "& > *, & > .MuiDialogContent-root": { px: { xs: 2, sm: 4 } }, - display: "flex", - flexDirection: "column", - - "& .MuiTypography-inherit, & .MuiDialogContent-root": { - typography: "body1", - }, - }} - > - {stepId === "welcome" ? null : !isMobile ? ( - - {listedSteps.map(({ id, shortTitle }, i) => ( - - setStepId(id)} - disabled={i > 0 && !completion[listedSteps[i - 1]?.id]} - sx={{ py: 2, my: -2, borderRadius: 1 }} - > - {shortTitle} - - - ))} - - ) : ( - setStepId(steps[stepIndex - 1].id)} - > - - - } - nextButton={ - setStepId(steps[stepIndex + 1].id)} - > - - - } - position="static" - sx={{ - background: "none", - p: 0, - "& .MuiMobileStepper-dot": { mx: 0.5 }, - }} - /> - )} - - {step.layout === "centered" ? ( - - - {stepId === "welcome" && ( - - - - - - )} - - - - - {step.title} - - - - - - - - {step.body} - - - - - - ) : ( - <> - - - - {step.title} - - - - - - - - {step.body} - - - - - )} - - {step.layout !== "centered" && ( -
{ - e.preventDefault(); - try { - handleContinue(); - } catch (e: any) { - throw new Error(e.message); - } - return false; - }} - > - - - -
- )} -
-
- ); -} From 6ca5ff45d5fcebfdd36fe575f5cc6eb9eec0bac8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Mar 2022 20:15:30 +0000 Subject: [PATCH 18/18] Bump url-parse from 1.4.7 to 1.5.10 Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.4.7 to 1.5.10. - [Release notes](https://github.com/unshiftio/url-parse/releases) - [Commits](https://github.com/unshiftio/url-parse/compare/1.4.7...1.5.10) --- updated-dependencies: - dependency-name: url-parse dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/yarn.lock b/yarn.lock index badf1454..47a669b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14052,9 +14052,9 @@ querystring@^0.2.0: integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== querystringify@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" - integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== queue-microtask@^1.2.2: version "1.2.3" @@ -17108,18 +17108,10 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-parse@^1.4.3: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - -url-parse@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b" - integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q== +url-parse@^1.4.3, url-parse@^1.5.1: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0"