mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-28 16:06:41 +01:00
finish setup
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
"@mui/material": "^5.0.0",
|
||||
"@mui/styles": "^5.0.0",
|
||||
"@rowy/form-builder": "^0.1.2",
|
||||
"@rowy/multiselect": "^0.1.6",
|
||||
"@rowy/multiselect": "^0.1.7",
|
||||
"@tinymce/tinymce-react": "^3.4.0",
|
||||
"algoliasearch": "^4.8.6",
|
||||
"ansi-to-react": "^6.1.5",
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function ScrollableDialogContent({
|
||||
style={{
|
||||
visibility: scrollInfo.y.percentage > 0 ? "visible" : "hidden",
|
||||
}}
|
||||
sx={{ mb: "-1px", ...dividerSx, ...topDividerSx }}
|
||||
sx={{ ...dividerSx, ...topDividerSx }}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function ScrollableDialogContent({
|
||||
style={{
|
||||
visibility: scrollInfo.y.percentage < 1 ? "visible" : "hidden",
|
||||
}}
|
||||
sx={{ mt: "-1px", ...dividerSx, ...bottomDividerSx }}
|
||||
sx={{ ...dividerSx, ...bottomDividerSx }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -20,6 +20,7 @@ export default function SetupItem({
|
||||
alignItems="flex-start"
|
||||
aria-busy={status === "loading"}
|
||||
aria-describedby={status === "loading" ? "progress" : undefined}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{status === "complete" ? (
|
||||
<CheckIcon aria-label="Item complete" color="action" />
|
||||
|
||||
@@ -5,7 +5,7 @@ import OpenInNewIcon from "components/InlineOpenInNewIcon";
|
||||
|
||||
import { useAppContext } from "contexts/AppContext";
|
||||
|
||||
export default function Welcome({
|
||||
export default function Step0Welcome({
|
||||
completion,
|
||||
setCompletion,
|
||||
}: ISetupStepBodyProps) {
|
||||
@@ -18,7 +18,7 @@ export default function Welcome({
|
||||
Get up and running in around 5 minutes.
|
||||
</Typography>
|
||||
<Typography variant="body1" paragraph>
|
||||
You’ll easily set up backend functionality, Firestore Rules, and user
|
||||
You’ll easily set up back-end functionality, Firestore Rules, and user
|
||||
management.
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
|
||||
@@ -160,6 +160,7 @@ export const checkServiceAccount = async (
|
||||
const res = await rowyRun({
|
||||
rowyRunUrl,
|
||||
route: runRoutes.serviceAccountAccess,
|
||||
signal,
|
||||
});
|
||||
return Object.values(res).reduce(
|
||||
(acc, value) => acc && value,
|
||||
|
||||
240
src/components/Setup/Step4Rules.tsx
Normal file
240
src/components/Setup/Step4Rules.tsx
Normal file
@@ -0,0 +1,240 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { ISetupStepBodyProps } from "pages/Setup";
|
||||
|
||||
import {
|
||||
Typography,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
Button,
|
||||
Link,
|
||||
TextField,
|
||||
Grid,
|
||||
} from "@mui/material";
|
||||
import LoadingButton from "@mui/lab/LoadingButton";
|
||||
import CopyIcon from "assets/icons/Copy";
|
||||
import InlineOpenInNewIcon from "components/InlineOpenInNewIcon";
|
||||
|
||||
import SetupItem from "./SetupItem";
|
||||
import SignInWithGoogle from "./SignInWithGoogle";
|
||||
|
||||
import { name } from "@root/package.json";
|
||||
import { useAppContext } from "contexts/AppContext";
|
||||
import { CONFIG } from "config/dbPaths";
|
||||
import { requiredRules, adminRules, utilFns } from "config/firestoreRules";
|
||||
import { rowyRun } from "utils/rowyRun";
|
||||
import { runRoutes } from "constants/runRoutes";
|
||||
|
||||
export default function Step4Rules({
|
||||
rowyRunUrl,
|
||||
completion,
|
||||
setCompletion,
|
||||
}: ISetupStepBodyProps) {
|
||||
const { projectId, currentUser, getAuthToken } = useAppContext();
|
||||
|
||||
const [hasRules, setHasRules] = useState(completion.rules);
|
||||
const [adminRule, setAdminRule] = useState(true);
|
||||
|
||||
const rules = `${
|
||||
adminRule ? adminRules : ""
|
||||
}${requiredRules}${utilFns}`.replace("\n", "");
|
||||
|
||||
const [currentRules, setCurrentRules] = useState("");
|
||||
useEffect(() => {
|
||||
if (rowyRunUrl && !hasRules && !currentRules)
|
||||
getAuthToken()
|
||||
.then((authToken) =>
|
||||
rowyRun({
|
||||
rowyRunUrl,
|
||||
route: runRoutes.firestoreRules,
|
||||
authToken,
|
||||
})
|
||||
)
|
||||
.then((data) => setCurrentRules(data?.source?.[0]?.content ?? ""));
|
||||
}, [rowyRunUrl, hasRules, currentRules, getAuthToken]);
|
||||
|
||||
const [newRules, setNewRules] = useState("");
|
||||
useEffect(() => {
|
||||
const inserted = currentRules.replace(
|
||||
/match\s*\/databases\/\{database\}\/documents\s*\{/,
|
||||
`match /databases/{database}/documents {\n` + rules
|
||||
);
|
||||
setNewRules(inserted);
|
||||
}, [currentRules, rules]);
|
||||
|
||||
const [rulesStatus, setRulesStatus] = useState<"IDLE" | "LOADING" | string>(
|
||||
"IDLE"
|
||||
);
|
||||
const setRules = async () => {
|
||||
setRulesStatus("LOADING");
|
||||
try {
|
||||
const authToken = await getAuthToken();
|
||||
if (!authToken) throw new Error("Failed to generate auth token");
|
||||
|
||||
const res = await rowyRun({
|
||||
rowyRunUrl,
|
||||
route: runRoutes.setFirestoreRules,
|
||||
authToken,
|
||||
body: { ruleset: newRules },
|
||||
});
|
||||
if (!res.success) throw new Error(res.message);
|
||||
|
||||
const isSuccessful = await checkRules(rowyRunUrl, authToken);
|
||||
if (isSuccessful) {
|
||||
setCompletion((c) => ({ ...c, rules: true }));
|
||||
setHasRules(true);
|
||||
}
|
||||
|
||||
setRulesStatus("IDLE");
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
setRulesStatus(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography variant="inherit">
|
||||
{name} configuration is stored in the <code>{CONFIG}</code> collection
|
||||
on Firestore. Your users will need read access to this collection and
|
||||
admins will need write access.
|
||||
</Typography>
|
||||
|
||||
<SetupItem
|
||||
status={hasRules ? "complete" : "incomplete"}
|
||||
title={
|
||||
hasRules
|
||||
? "Firestore Rules are set up."
|
||||
: "Add the following rules to enable access to Rowy configuration:"
|
||||
}
|
||||
>
|
||||
{!hasRules && (
|
||||
<>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={adminRule}
|
||||
onChange={(e) => setAdminRule(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Allow admins to read and write all documents"
|
||||
sx={{ "&&": { ml: -11 / 8, mb: -11 / 8 }, width: "100%" }}
|
||||
/>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
component="pre"
|
||||
sx={{
|
||||
width: { sm: "100%", md: 840 - 72 - 32 },
|
||||
height: 136,
|
||||
resize: "both",
|
||||
overflow: "auto",
|
||||
|
||||
"& .comment": { color: "info.dark" },
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: rules.replace(
|
||||
/(\/\/.*$)/gm,
|
||||
`<span class="comment">$1</span>`
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
startIcon={<CopyIcon />}
|
||||
onClick={() => navigator.clipboard.writeText(rules)}
|
||||
>
|
||||
Copy to Clipboard
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</SetupItem>
|
||||
|
||||
{!hasRules && (
|
||||
<SetupItem
|
||||
status="incomplete"
|
||||
title={
|
||||
<>
|
||||
You can add these rules{" "}
|
||||
<Link
|
||||
href={`https://console.firebase.google.com/project/${
|
||||
projectId || "_"
|
||||
}/firestore/rules`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
in the Firebase Console
|
||||
<InlineOpenInNewIcon />
|
||||
</Link>{" "}
|
||||
or directly below:
|
||||
</>
|
||||
}
|
||||
>
|
||||
<TextField
|
||||
id="new-rules"
|
||||
label="New Rules"
|
||||
value={newRules}
|
||||
onChange={(e) => setNewRules(e.target.value)}
|
||||
multiline
|
||||
rows={5}
|
||||
fullWidth
|
||||
sx={{
|
||||
"& .MuiInputBase-input": {
|
||||
fontFamily: "mono",
|
||||
letterSpacing: 0,
|
||||
resize: "vertical",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<LoadingButton
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={setRules}
|
||||
loading={rulesStatus === "LOADING"}
|
||||
>
|
||||
Set Firestore Rules
|
||||
</LoadingButton>
|
||||
|
||||
{rulesStatus !== "LOADING" && typeof rulesStatus === "string" && (
|
||||
<Typography variant="caption" color="error">
|
||||
{rulesStatus}
|
||||
</Typography>
|
||||
)}
|
||||
</SetupItem>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const checkRules = async (
|
||||
rowyRunUrl: string,
|
||||
authToken: string,
|
||||
signal?: AbortSignal
|
||||
) => {
|
||||
if (!authToken) return false;
|
||||
|
||||
try {
|
||||
const res = await rowyRun({
|
||||
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(
|
||||
requiredRules.replace(/\s{2,}/g, " ").replace(/\n/g, " ")
|
||||
) &&
|
||||
sanitizedRules.includes(
|
||||
utilFns.replace(/\s{2,}/g, " ").replace(/\n/g, " ")
|
||||
);
|
||||
|
||||
return hasRules;
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
115
src/components/Setup/Step5Migrate.tsx
Normal file
115
src/components/Setup/Step5Migrate.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { ISetupStepBodyProps } from "pages/Setup";
|
||||
|
||||
import { Typography, Button } from "@mui/material";
|
||||
import LoadingButton from "@mui/lab/LoadingButton";
|
||||
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
|
||||
|
||||
import SetupItem from "./SetupItem";
|
||||
import SignInWithGoogle from "./SignInWithGoogle";
|
||||
|
||||
import { name } from "@root/package.json";
|
||||
import { useAppContext } from "contexts/AppContext";
|
||||
import { CONFIG } from "config/dbPaths";
|
||||
import { rowyRun } from "utils/rowyRun";
|
||||
import { runRoutes } from "constants/runRoutes";
|
||||
|
||||
export default function Step5Migrate({
|
||||
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,
|
||||
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 (
|
||||
<>
|
||||
<Typography variant="inherit">
|
||||
It looks like you’ve previously configured your Firestore database for
|
||||
Firetable. You can migrate this configuration, including your XX tables
|
||||
to {name}.
|
||||
</Typography>
|
||||
|
||||
<SetupItem
|
||||
status={status === true ? "complete" : "incomplete"}
|
||||
title={
|
||||
status === true ? (
|
||||
<>
|
||||
Configuration migrated to the <code>{CONFIG}</code> collection.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Migrate your configuration to the <code>{CONFIG}</code>{" "}
|
||||
collection.
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
{status !== true && (
|
||||
<>
|
||||
<LoadingButton
|
||||
variant="contained"
|
||||
color="primary"
|
||||
loading={status === "LOADING"}
|
||||
onClick={migrate}
|
||||
>
|
||||
Migrate
|
||||
</LoadingButton>
|
||||
{status !== "LOADING" && typeof status === "string" && (
|
||||
<Typography variant="caption" color="error">
|
||||
{status}
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</SetupItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const checkMigrate = async (
|
||||
rowyRunUrl: string,
|
||||
authToken: string,
|
||||
signal?: AbortSignal
|
||||
) => {
|
||||
if (!authToken) return false;
|
||||
|
||||
try {
|
||||
const res = await rowyRun({
|
||||
rowyRunUrl,
|
||||
route: runRoutes.checkFT2Rowy,
|
||||
authToken,
|
||||
signal,
|
||||
});
|
||||
return res.migrationRequired;
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
68
src/components/Setup/Step6Finish.tsx
Normal file
68
src/components/Setup/Step6Finish.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import { useState } from "react";
|
||||
import { useSnackbar } from "notistack";
|
||||
|
||||
import { Typography, Stack, RadioGroup, Radio } from "@mui/material";
|
||||
import ThumbUpIcon from "@mui/icons-material/ThumbUpAlt";
|
||||
import ThumbUpOffIcon from "@mui/icons-material/ThumbUpOffAlt";
|
||||
import ThumbDownIcon from "@mui/icons-material/ThumbDownAlt";
|
||||
import ThumbDownOffIcon from "@mui/icons-material/ThumbDownOffAlt";
|
||||
|
||||
import { name } from "@root/package.json";
|
||||
import { analytics } from "analytics";
|
||||
|
||||
export default function Step6Finish() {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [rating, setRating] = useState<"up" | "down" | undefined>();
|
||||
|
||||
const handleRate = (e) => {
|
||||
setRating(e.target.value);
|
||||
analytics.logEvent("rate_setup", { rating: e.target.value });
|
||||
enqueueSnackbar("Thanks for your feedback!");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography variant="body1" gutterBottom>
|
||||
You can now continue to {name} and create a table from your Firestore
|
||||
collections.
|
||||
</Typography>
|
||||
|
||||
<Stack
|
||||
component="fieldset"
|
||||
spacing={1}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
sx={{ appearance: "none", p: 0, m: 0, border: "none" }}
|
||||
>
|
||||
<Typography variant="body1" component="legend">
|
||||
How was your setup experience?
|
||||
</Typography>
|
||||
|
||||
<RadioGroup
|
||||
style={{ flexDirection: "row" }}
|
||||
value={rating}
|
||||
onChange={handleRate}
|
||||
>
|
||||
<Radio
|
||||
checkedIcon={<ThumbUpIcon />}
|
||||
icon={<ThumbUpOffIcon />}
|
||||
inputProps={{ "aria-label": "Thumbs up" }}
|
||||
value="up"
|
||||
color="secondary"
|
||||
disabled={rating !== undefined}
|
||||
/>
|
||||
<Radio
|
||||
checkedIcon={<ThumbDownIcon />}
|
||||
icon={<ThumbDownOffIcon />}
|
||||
inputProps={{ "aria-label": "Thumbs down" }}
|
||||
value="down"
|
||||
color="secondary"
|
||||
disabled={rating !== undefined}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
export const SETTINGS = "_rowy_/settings";
|
||||
export const PUBLIC_SETTINGS = "_rowy_/publicSettings";
|
||||
export const CONFIG = "_rowy_" as const;
|
||||
|
||||
export const TABLE_SCHEMAS = SETTINGS + "/schema";
|
||||
export const TABLE_GROUP_SCHEMAS = SETTINGS + "/groupSchema";
|
||||
export const SETTINGS = `${CONFIG}/settings` as const;
|
||||
export const PUBLIC_SETTINGS = `${CONFIG}/publicSettings` as const;
|
||||
|
||||
export const USER_MANAGEMENT = "_rowy_/userManagement";
|
||||
export const USERS = USER_MANAGEMENT + "/users";
|
||||
export const TABLE_SCHEMAS = `${SETTINGS}/schema` as const;
|
||||
export const TABLE_GROUP_SCHEMAS = `${SETTINGS}/groupSchema` as const;
|
||||
|
||||
export const USER_MANAGEMENT = `${CONFIG}/userManagement` as const;
|
||||
export const USERS = `${USER_MANAGEMENT}/users` as const;
|
||||
|
||||
39
src/config/firestoreRules.ts
Normal file
39
src/config/firestoreRules.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { CONFIG, USERS, PUBLIC_SETTINGS } from "./dbPaths";
|
||||
|
||||
export const requiredRules = `
|
||||
// Rowy: Allow signed in users to read Rowy configuration and admins to write
|
||||
match /${CONFIG}/{docId} {
|
||||
allow read: if request.auth != null;
|
||||
allow write: if hasAnyRole(["ADMIN", "OWNER"]);
|
||||
match /{document=**} {
|
||||
allow read: if request.auth != null;
|
||||
allow write: if hasAnyRole(["ADMIN", "OWNER"]);
|
||||
}
|
||||
}
|
||||
// Rowy: Allow users to edit their settings
|
||||
match /${USERS}/{userId} {
|
||||
allow get, update, delete: if isDocOwner(userId);
|
||||
allow create: if request.auth != null;
|
||||
}
|
||||
// Rowy: Allow public to read public Rowy configuration
|
||||
match /${PUBLIC_SETTINGS} {
|
||||
allow get: if true;
|
||||
}
|
||||
` as const;
|
||||
|
||||
export const adminRules = `
|
||||
// Allow admins to read and write all documents
|
||||
match /{document=**} {
|
||||
allow read, write: if hasAnyRole(["ADMIN", "OWNER"]);
|
||||
}
|
||||
` as const;
|
||||
|
||||
export const utilFns = `
|
||||
// Rowy: Utility functions
|
||||
function isDocOwner(docId) {
|
||||
return request.auth != null && (request.auth.uid == resource.id || request.auth.uid == docId);
|
||||
}
|
||||
function hasAnyRole(roles) {
|
||||
return request.auth != null && request.auth.token.roles.hasAny(roles);
|
||||
}
|
||||
` as const;
|
||||
@@ -31,18 +31,21 @@ type actionScriptRequest = {
|
||||
|
||||
export type runRouteRequest = actionScriptRequest | impersonateUserRequest;
|
||||
|
||||
export const runRoutes: Record<string, RunRoute> = {
|
||||
impersonateUser: { path: "/impersonateUser", method: "GET" },
|
||||
version: { path: "/version", method: "GET" },
|
||||
region: { path: "/region", method: "GET" },
|
||||
firestoreRules: { path: "/firestoreRules", method: "GET" },
|
||||
setFirestoreRules: { path: "/setFirestoreRules", method: "POST" },
|
||||
listCollections: { path: "/listCollections", method: "GET" },
|
||||
serviceAccountAccess: { path: "/serviceAccountAccess", method: "GET" },
|
||||
checkFT2Rowy: { path: "/checkFT2Rowy", method: "GET" },
|
||||
migrateFT2Rowy: { path: "/migrateFT2Rowy", method: "GET" },
|
||||
actionScript: { path: "/actionScript", method: "POST" },
|
||||
buildFunction: { path: "/buildFunction", method: "POST" },
|
||||
projectOwner: { path: "/projectOwner", method: "GET" },
|
||||
setOwnerRoles: { path: "/setOwnerRoles", method: "GET" },
|
||||
};
|
||||
export const runRoutes = {
|
||||
impersonateUser: { path: "/impersonateUser", method: "GET" } as RunRoute,
|
||||
version: { path: "/version", method: "GET" } as RunRoute,
|
||||
region: { path: "/region", method: "GET" } as RunRoute,
|
||||
firestoreRules: { path: "/firestoreRules", method: "GET" } as RunRoute,
|
||||
setFirestoreRules: { path: "/setFirestoreRules", method: "POST" } as RunRoute,
|
||||
listCollections: { path: "/listCollections", method: "GET" } as RunRoute,
|
||||
serviceAccountAccess: {
|
||||
path: "/serviceAccountAccess",
|
||||
method: "GET",
|
||||
} as RunRoute,
|
||||
checkFT2Rowy: { path: "/checkFT2Rowy", method: "GET" } as RunRoute,
|
||||
migrateFT2Rowy: { path: "/migrateFT2Rowy", method: "GET" } as RunRoute,
|
||||
actionScript: { path: "/actionScript", method: "POST" } as RunRoute,
|
||||
buildFunction: { path: "/buildFunction", method: "POST" } as RunRoute,
|
||||
projectOwner: { path: "/projectOwner", method: "GET" } as RunRoute,
|
||||
setOwnerRoles: { path: "/setOwnerRoles", method: "GET" } as RunRoute,
|
||||
} as const;
|
||||
|
||||
@@ -34,6 +34,9 @@ import Step1RowyRun, { checkRowyRun } from "components/Setup/Step1RowyRun";
|
||||
import Step2ServiceAccount, { checkServiceAccount } from "components/Setup/Step2ServiceAccount";
|
||||
// prettier-ignore
|
||||
import Step3ProjectOwner, { checkProjectOwner } from "@src/components/Setup/Step3ProjectOwner";
|
||||
import Step4Rules, { checkRules } from "components/Setup/Step4Rules";
|
||||
import Step5Migrate, { checkMigrate } from "components/Setup/Step5Migrate";
|
||||
import Step6Finish from "components/Setup/Step6Finish";
|
||||
|
||||
import { name } from "@root/package.json";
|
||||
import routes from "constants/routes";
|
||||
@@ -60,6 +63,7 @@ const checkAllSteps = async (
|
||||
rowyRunUrl: string,
|
||||
currentUser: firebase.default.User | null | undefined,
|
||||
userRoles: string[] | null,
|
||||
authToken: string,
|
||||
signal: AbortSignal
|
||||
) => {
|
||||
console.log("Check all steps");
|
||||
@@ -69,23 +73,30 @@ const checkAllSteps = async (
|
||||
if (rowyRunValidation.isValidRowyRunUrl) {
|
||||
if (rowyRunValidation.isLatestVersion) completion.rowyRun = true;
|
||||
|
||||
const serviceAccount = await checkServiceAccount(rowyRunUrl, signal);
|
||||
if (serviceAccount) completion.serviceAccount = true;
|
||||
|
||||
const projectOwner = await checkProjectOwner(
|
||||
rowyRunUrl,
|
||||
currentUser,
|
||||
userRoles,
|
||||
signal
|
||||
);
|
||||
if (projectOwner) completion.projectOwner = true;
|
||||
const promises = [
|
||||
checkServiceAccount(rowyRunUrl, signal).then((serviceAccount) => {
|
||||
if (serviceAccount) completion.serviceAccount = true;
|
||||
}),
|
||||
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 } = useAppContext();
|
||||
const { currentUser, userRoles, getAuthToken } = useAppContext();
|
||||
|
||||
const fullScreenHeight = use100vh() ?? 0;
|
||||
const isMobile = useMediaQuery((theme: any) => theme.breakpoints.down("sm"));
|
||||
@@ -110,16 +121,24 @@ export default function SetupPage() {
|
||||
|
||||
if (rowyRunUrl) {
|
||||
setCheckingAllSteps(true);
|
||||
checkAllSteps(rowyRunUrl, currentUser, userRoles, signal).then(
|
||||
(result) => {
|
||||
if (!signal.aborted) setCompletion((c) => ({ ...c, ...result }));
|
||||
setCheckingAllSteps(false);
|
||||
}
|
||||
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]);
|
||||
}, [rowyRunUrl, currentUser, userRoles, getAuthToken]);
|
||||
|
||||
const stepProps = { completion, setCompletion, checkAllSteps, rowyRunUrl };
|
||||
|
||||
@@ -176,14 +195,14 @@ export default function SetupPage() {
|
||||
id: "rules",
|
||||
shortTitle: `Rules`,
|
||||
title: `Set Up Firestore Rules`,
|
||||
body: `x`,
|
||||
body: <Step4Rules {...stepProps} />,
|
||||
},
|
||||
completion.migrate !== undefined
|
||||
? {
|
||||
id: "migrate",
|
||||
shortTitle: `Migrate`,
|
||||
title: `Migrate to ${name} (optional)`,
|
||||
body: `x`,
|
||||
body: <Step5Migrate {...stepProps} />,
|
||||
}
|
||||
: ({} as ISetupStep),
|
||||
{
|
||||
@@ -191,21 +210,17 @@ export default function SetupPage() {
|
||||
layout: "centered" as "centered",
|
||||
shortTitle: `Finish`,
|
||||
title: `You’re all set up!`,
|
||||
body: (
|
||||
<div>
|
||||
You can now create a table from your Firestore collections or continue
|
||||
to {name}
|
||||
</div>
|
||||
),
|
||||
body: <Step6Finish />,
|
||||
actions: (
|
||||
<>
|
||||
<Button variant="contained" color="primary">
|
||||
Create Table
|
||||
</Button>
|
||||
<Button component={Link} to={routes.home} sx={{ ml: 1 }}>
|
||||
Continue to {name}
|
||||
</Button>
|
||||
</>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
component={Link}
|
||||
to={routes.home}
|
||||
sx={{ ml: 1 }}
|
||||
>
|
||||
Continue to {name}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
].filter((x) => x.id);
|
||||
@@ -239,7 +254,7 @@ export default function SetupPage() {
|
||||
backdropFilter: "blur(20px) saturate(150%)",
|
||||
|
||||
maxWidth: 840,
|
||||
width: "100%",
|
||||
width: (theme) => `calc(100vw - ${theme.spacing(2)})`,
|
||||
maxHeight: (theme) =>
|
||||
`calc(${
|
||||
fullScreenHeight > 0 ? `${fullScreenHeight}px` : "100vh"
|
||||
@@ -249,7 +264,7 @@ export default function SetupPage() {
|
||||
height: 840 * 0.75,
|
||||
|
||||
p: 0,
|
||||
"& > *": { px: { xs: 2, sm: 4 } },
|
||||
"& > *, & > .MuiDialogContent-root": { px: { xs: 2, sm: 4 } },
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
|
||||
@@ -373,7 +388,10 @@ export default function SetupPage() {
|
||||
|
||||
<SwitchTransition mode="out-in">
|
||||
<SlideTransition key={stepId} appear timeout={100}>
|
||||
<ScrollableDialogContent disableTopDivider>
|
||||
<ScrollableDialogContent
|
||||
disableTopDivider={step.layout === "centered"}
|
||||
sx={{ overflowX: "auto" }}
|
||||
>
|
||||
<Stack spacing={4}>{step.body}</Stack>
|
||||
</ScrollableDialogContent>
|
||||
</SlideTransition>
|
||||
|
||||
@@ -42,14 +42,29 @@ export const components = (theme: Theme): ThemeOptions => {
|
||||
components: {
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: {
|
||||
code: {
|
||||
// https://css-tricks.com/revisiting-prefers-reduced-motion-the-reduced-motion-media-query/
|
||||
"@media screen and (prefers-reduced-motion: reduce), (update: slow)":
|
||||
{
|
||||
"*:not(.MuiCircularProgress-root *):not(.MuiLinearProgress-root *)":
|
||||
{
|
||||
animationDuration: "0.001ms !important",
|
||||
animationIteratonCount: "1 !important",
|
||||
transitionDuration: "0.001ms !important",
|
||||
scrollBehavior: "auto !important",
|
||||
},
|
||||
},
|
||||
|
||||
"code, pre, pre.MuiTypography-root": {
|
||||
fontFamily: theme.typography.fontFamilyMono,
|
||||
letterSpacing: 0,
|
||||
|
||||
backgroundColor: theme.palette.action.selected,
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
padding: `${1 / 16}em ${4 / 16}em`,
|
||||
},
|
||||
"pre, pre.MuiTypography-root": {
|
||||
padding: `${4 / 16}em ${8 / 16}em`,
|
||||
},
|
||||
|
||||
".chrome-picker": {
|
||||
colorScheme: "light",
|
||||
@@ -818,7 +833,7 @@ export const components = (theme: Theme): ThemeOptions => {
|
||||
MuiStepIcon: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: theme.palette.action.selected,
|
||||
color: theme.palette.action.hover,
|
||||
"&.Mui-completed:not(.Mui-active)": {
|
||||
color: theme.palette.text.disabled,
|
||||
},
|
||||
|
||||
@@ -2672,10 +2672,10 @@
|
||||
use-debounce "^3.4.3"
|
||||
yup "^0.32.9"
|
||||
|
||||
"@rowy/multiselect@^0.1.6":
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@rowy/multiselect/-/multiselect-0.1.6.tgz#a1edfbc67d4f9267cb73a3d33d5a2570662b74d1"
|
||||
integrity sha512-NxyskBT/8GA1ARtWv1XSBr8ltfmArhnETujbf/PgT7sRtDhv5dFB/XNSMH46rNYuN6zGmG1jHR/pIRs2fTy08w==
|
||||
"@rowy/multiselect@^0.1.7":
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@rowy/multiselect/-/multiselect-0.1.7.tgz#d751cf12886a560f25ba9aa98908ab1aa124b78b"
|
||||
integrity sha512-sCvnWl5sP6W5N3NQI360diu+Iktxh4VmsaiHmTk9Y85BdPPjeTTKcTuKqbewZsYXDkk4gEcxOpx5XD00Ap9xhw==
|
||||
|
||||
"@sindresorhus/is@^0.14.0":
|
||||
version "0.14.0"
|
||||
|
||||
Reference in New Issue
Block a user