mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
Setup: add step 1
This commit is contained in:
@@ -102,7 +102,6 @@ export default function App() {
|
||||
routes.home,
|
||||
routes.tableWithId,
|
||||
routes.tableGroupWithId,
|
||||
routes.gridWithId,
|
||||
routes.settings,
|
||||
routes.projectSettings,
|
||||
routes.userSettings,
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { SVGProps } from "react";
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
export default function Logo(props: SVGProps<SVGSVGElement>) {
|
||||
export interface ILogoProps extends SVGProps<SVGSVGElement> {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export default function Logo({ size = 1.5, ...props }: ILogoProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="102"
|
||||
height="32"
|
||||
width={Math.round(68 * size)}
|
||||
height={Math.round(21 * size)}
|
||||
viewBox="0 -1.5 68 21"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby="rowy-logo-title"
|
||||
|
||||
@@ -6,7 +6,13 @@ export default function InlineOpenInNewIcon(props: SvgIconProps) {
|
||||
<OpenInNewIcon
|
||||
aria-label="Open in new tab"
|
||||
{...props}
|
||||
sx={{ fontSize: 16, verticalAlign: "text-bottom", ml: 0.5, ...props.sx }}
|
||||
sx={{
|
||||
fontSize: 16,
|
||||
verticalAlign: "text-bottom",
|
||||
ml: 0.5,
|
||||
opacity: 0.6,
|
||||
...props.sx,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,31 +35,25 @@ export default function ScrollableDialogContent({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider
|
||||
style={{
|
||||
visibility:
|
||||
!disableTopDivider &&
|
||||
scrollInfo.y.percentage !== null &&
|
||||
scrollInfo.y.percentage > 0
|
||||
? "visible"
|
||||
: "hidden",
|
||||
}}
|
||||
sx={{ ...dividerSx, ...topDividerSx }}
|
||||
/>
|
||||
{!disableTopDivider && scrollInfo.y.percentage !== null && (
|
||||
<Divider
|
||||
style={{
|
||||
visibility: scrollInfo.y.percentage > 0 ? "visible" : "hidden",
|
||||
}}
|
||||
sx={{ mb: "-1px", ...dividerSx, ...topDividerSx }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<MemoizedDialogContent {...props} setRef={setRef} />
|
||||
|
||||
<Divider
|
||||
style={{
|
||||
visibility:
|
||||
!disableBottomDivider &&
|
||||
scrollInfo.y.percentage !== null &&
|
||||
scrollInfo.y.percentage < 1
|
||||
? "visible"
|
||||
: "hidden",
|
||||
}}
|
||||
sx={{ ...dividerSx, ...bottomDividerSx }}
|
||||
/>
|
||||
{!disableBottomDivider && scrollInfo.y.percentage !== null && (
|
||||
<Divider
|
||||
style={{
|
||||
visibility: scrollInfo.y.percentage < 1 ? "visible" : "hidden",
|
||||
}}
|
||||
sx={{ mt: "-1px", ...dividerSx, ...bottomDividerSx }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,15 +41,15 @@ export default function About() {
|
||||
.replace("github.com", "api.github.com/repos")
|
||||
.replace(/.git$/, "/releases/latest");
|
||||
try {
|
||||
const res = await fetch(endpoint, {
|
||||
const req = await fetch(endpoint, {
|
||||
headers: {
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
});
|
||||
const json = await res.json();
|
||||
const res = await req.json();
|
||||
|
||||
if (json.tag_name > "v" + version) {
|
||||
setLatestUpdate(json);
|
||||
if (res.tag_name > "v" + version) {
|
||||
setLatestUpdate(res);
|
||||
setCheckState(null);
|
||||
} else {
|
||||
setCheckState("NO_UPDATE");
|
||||
|
||||
@@ -4,17 +4,19 @@ import InlineOpenInNewIcon from "components/InlineOpenInNewIcon";
|
||||
|
||||
import { IProjectSettingsChildProps } from "pages/Settings/ProjectSettings";
|
||||
import WIKI_LINKS from "constants/wikiLinks";
|
||||
import { name, repository } from "@root/package.json";
|
||||
import { name } from "@root/package.json";
|
||||
import { runRepoUrl } from "constants/runRoutes";
|
||||
|
||||
export default function CloudRun({
|
||||
export default function rowyRun({
|
||||
settings,
|
||||
updateSettings,
|
||||
}: IProjectSettingsChildProps) {
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
{name} Run is a Cloud Run instance that deploys this project’s Cloud
|
||||
Functions.{" "}
|
||||
{name} Run is a Cloud Run instance that provides back-end functionality,
|
||||
such as table action scripts, user management, and easy Cloud Function
|
||||
deployment.{" "}
|
||||
<Link
|
||||
href={WIKI_LINKS.functions}
|
||||
target="_blank"
|
||||
@@ -41,18 +43,15 @@ export default function CloudRun({
|
||||
|
||||
<Grid item>
|
||||
<LoadingButton
|
||||
href={`https://deploy.cloud.run/?git_repo=${repository.url
|
||||
.split("/")
|
||||
.slice(0, -1)
|
||||
.join("/")}/FunctionsBuilder.git`}
|
||||
href={`https://deploy.cloud.run/?git_repo=${runRepoUrl}.git`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
loading={
|
||||
settings.cloudRunDeployStatus === "BUILDING" ||
|
||||
settings.cloudRunDeployStatus === "COMPLETE"
|
||||
settings.rowyRunDeployStatus === "BUILDING" ||
|
||||
settings.rowyRunDeployStatus === "COMPLETE"
|
||||
}
|
||||
loadingIndicator={
|
||||
settings.cloudRunDeployStatus === "COMPLETE"
|
||||
settings.rowyRunDeployStatus === "COMPLETE"
|
||||
? "Deployed"
|
||||
: undefined
|
||||
}
|
||||
@@ -65,9 +64,9 @@ export default function CloudRun({
|
||||
|
||||
<TextField
|
||||
label="Cloud Run Instance URL"
|
||||
id="cloudRunUrl"
|
||||
id="rowyRunUrl"
|
||||
defaultValue={settings.rowyRunUrl}
|
||||
onChange={(e) => updateSettings({ cloudRunUrl: e.target.value })}
|
||||
onChange={(e) => updateSettings({ rowyRunUrl: e.target.value })}
|
||||
fullWidth
|
||||
placeholder="https://<id>.run.app"
|
||||
type="url"
|
||||
|
||||
44
src/components/Setup/SetupItem.tsx
Normal file
44
src/components/Setup/SetupItem.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Stack, CircularProgress, Typography } from "@mui/material";
|
||||
import CheckIcon from "@mui/icons-material/Check";
|
||||
import ArrowIcon from "@mui/icons-material/ArrowForward";
|
||||
|
||||
export interface ISetupItemProps {
|
||||
status: "complete" | "loading" | "incomplete";
|
||||
title: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function SetupItem({
|
||||
status,
|
||||
title,
|
||||
children,
|
||||
}: ISetupItemProps) {
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={2}
|
||||
alignItems="flex-start"
|
||||
aria-busy={status === "loading"}
|
||||
aria-describedby={status === "loading" ? "progress" : undefined}
|
||||
>
|
||||
{status === "complete" ? (
|
||||
<CheckIcon aria-label="Item complete" color="action" />
|
||||
) : status === "loading" ? (
|
||||
<CircularProgress
|
||||
id="progress"
|
||||
size={20}
|
||||
thickness={5}
|
||||
sx={{ m: 0.25 }}
|
||||
/>
|
||||
) : (
|
||||
<ArrowIcon aria-label="Item" color="primary" />
|
||||
)}
|
||||
|
||||
<Stack spacing={2} alignItems="flex-start">
|
||||
<Typography variant="inherit">{title}</Typography>
|
||||
|
||||
{children}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
38
src/components/Setup/Step0Welcome.tsx
Normal file
38
src/components/Setup/Step0Welcome.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { ISetupStepBodyProps } from "pages/Setup";
|
||||
|
||||
import { FormControlLabel, Checkbox, Typography, Link } from "@mui/material";
|
||||
import OpenInNewIcon from "components/InlineOpenInNewIcon";
|
||||
|
||||
export default function Welcome({
|
||||
completion,
|
||||
setCompletion,
|
||||
}: ISetupStepBodyProps) {
|
||||
return (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={completion.welcome}
|
||||
onChange={(e) =>
|
||||
setCompletion((c) => ({ ...c, welcome: e.target.checked }))
|
||||
}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<>
|
||||
<Typography sx={{ mt: 1.25, mb: 0.5 }}>
|
||||
I agree to the terms and conditions
|
||||
</Typography>
|
||||
<Link display="block" variant="body2" color="text.secondary">
|
||||
Read the simple English version
|
||||
<OpenInNewIcon />
|
||||
</Link>
|
||||
<Link display="block" variant="body2" color="text.secondary">
|
||||
Read the full terms and conditions
|
||||
<OpenInNewIcon />
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
sx={{ pr: 1, textAlign: "left", alignItems: "flex-start", p: 0, m: 0 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
175
src/components/Setup/Step1RowyRun.tsx
Normal file
175
src/components/Setup/Step1RowyRun.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useLocation, useHistory } from "react-router-dom";
|
||||
import queryString from "query-string";
|
||||
import { ISetupStepBodyProps } from "pages/Setup";
|
||||
|
||||
import { Button, Typography, Stack, TextField } from "@mui/material";
|
||||
import LoadingButton from "@mui/lab/LoadingButton";
|
||||
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
|
||||
|
||||
import SetupItem from "./SetupItem";
|
||||
|
||||
import { runRepoUrl, RunRoutes } from "constants/runRoutes";
|
||||
|
||||
export default function Step1RowyRun({
|
||||
completion,
|
||||
setCompletion,
|
||||
rowyRunUrl: paramsRowyRunUrl,
|
||||
}: ISetupStepBodyProps) {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const [isValidRowyRunUrl, setIsValidRowyRunUrl] = useState(false);
|
||||
const [isLatestVersion, setIsLatestVersion] = useState(false);
|
||||
|
||||
const [rowyRunUrl, setRowyRunUrl] = useState(paramsRowyRunUrl);
|
||||
const [latestVersion, setLatestVersion] = useState("");
|
||||
const [verificationStatus, setVerificationStatus] = useState<
|
||||
"idle" | "loading" | "pass" | "fail"
|
||||
>("idle");
|
||||
const verifyRowyRunUrl = async () => {
|
||||
setVerificationStatus("loading");
|
||||
try {
|
||||
const req = await fetch(rowyRunUrl + RunRoutes.version.path, {
|
||||
method: RunRoutes.version.method,
|
||||
});
|
||||
if (!req.ok) throw new Error("Request failed");
|
||||
const res = await req.json();
|
||||
if (!res.version) throw new Error("Invalid response");
|
||||
|
||||
// https://docs.github.com/en/rest/reference/repos#get-the-latest-release
|
||||
const endpoint =
|
||||
runRepoUrl.replace("github.com", "api.github.com/repos") +
|
||||
"/releases/latest";
|
||||
const latestVersionReq = await fetch(endpoint, {
|
||||
headers: { Accept: "application/vnd.github.v3+json" },
|
||||
});
|
||||
const latestVersion = await latestVersionReq.json();
|
||||
if (!latestVersion.tag_name) throw new Error("No releases");
|
||||
|
||||
if (latestVersion.tag_name > "v" + res.version) {
|
||||
setVerificationStatus("pass");
|
||||
setIsLatestVersion(false);
|
||||
setLatestVersion(latestVersion.tag_name);
|
||||
} else {
|
||||
setVerificationStatus("pass");
|
||||
setIsLatestVersion(true);
|
||||
setLatestVersion("v" + res.version);
|
||||
setCompletion((c) => ({ ...c, rowyRun: true }));
|
||||
}
|
||||
|
||||
setIsValidRowyRunUrl(true);
|
||||
history.replace({
|
||||
pathname,
|
||||
search: queryString.stringify({ rowyRunUrl }),
|
||||
});
|
||||
} catch (e: any) {
|
||||
console.error(`Failed to verify Rowy Run URL: ${e.message}`);
|
||||
setVerificationStatus("fail");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isValidRowyRunUrl && paramsRowyRunUrl) console.log(paramsRowyRunUrl);
|
||||
}, [paramsRowyRunUrl, isValidRowyRunUrl]);
|
||||
|
||||
const deployButton = window.location.hostname.includes("rowy.app") ? (
|
||||
<Button
|
||||
href={`https://deploy.cloud.run/?git_repo=${runRepoUrl}.git`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
endIcon={<OpenInNewIcon />}
|
||||
>
|
||||
One-Click Deploy
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
href={runRepoUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
endIcon={<OpenInNewIcon />}
|
||||
>
|
||||
Deploy Instructions
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SetupItem
|
||||
status={isValidRowyRunUrl ? "complete" : "incomplete"}
|
||||
title={
|
||||
isValidRowyRunUrl
|
||||
? `Rowy Run is set up at: ${rowyRunUrl}`
|
||||
: "Deploy Rowy Run to your GCP project."
|
||||
}
|
||||
>
|
||||
{!isValidRowyRunUrl && (
|
||||
<>
|
||||
{deployButton}
|
||||
|
||||
<div>
|
||||
<Typography variant="inherit" gutterBottom>
|
||||
Then paste the Rowy Run instance URL below:
|
||||
</Typography>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
alignItems="center"
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<TextField
|
||||
id="rowyRunUrl"
|
||||
label="Rowy Run Instance URL"
|
||||
placeholder="https://*.run.app"
|
||||
value={rowyRunUrl}
|
||||
onChange={(e) => setRowyRunUrl(e.target.value)}
|
||||
type="url"
|
||||
autoComplete="url"
|
||||
fullWidth
|
||||
error={verificationStatus === "fail"}
|
||||
helperText={
|
||||
verificationStatus === "fail" ? "Invalid URL" : " "
|
||||
}
|
||||
/>
|
||||
<LoadingButton
|
||||
variant="contained"
|
||||
color="primary"
|
||||
loading={verificationStatus === "loading"}
|
||||
onClick={verifyRowyRunUrl}
|
||||
>
|
||||
Verify
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</SetupItem>
|
||||
|
||||
{isValidRowyRunUrl && (
|
||||
<SetupItem
|
||||
status={isLatestVersion ? "complete" : "incomplete"}
|
||||
title={
|
||||
isLatestVersion
|
||||
? `Rowy Run is up to date: ${latestVersion}`
|
||||
: `Update your Rowy Run instance. Latest version: ${latestVersion}`
|
||||
}
|
||||
>
|
||||
{!isLatestVersion && (
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
{deployButton}
|
||||
<LoadingButton
|
||||
variant="contained"
|
||||
color="primary"
|
||||
loading={verificationStatus === "loading"}
|
||||
onClick={verifyRowyRunUrl}
|
||||
>
|
||||
Verify
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
)}
|
||||
</SetupItem>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -8,13 +8,9 @@ export enum routes {
|
||||
setup = "/setup",
|
||||
|
||||
table = "/table",
|
||||
tableGroup = "/tableGroup",
|
||||
|
||||
tableWithId = "/table/:id",
|
||||
tableGroup = "/tableGroup",
|
||||
tableGroupWithId = "/tableGroup/:id",
|
||||
grid = "/grid",
|
||||
gridWithId = "/grid/:id",
|
||||
editor = "/editor",
|
||||
|
||||
settings = "/settings",
|
||||
userSettings = "/settings/user",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export const runRepoUrl = "https://github.com/rowyio/rowyRun";
|
||||
|
||||
export type RunRoute = {
|
||||
path: string;
|
||||
method: "POST" | "GET";
|
||||
|
||||
@@ -1,29 +1,190 @@
|
||||
import { useRouteMatch } from "react-router-dom";
|
||||
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,
|
||||
StepLabel,
|
||||
StepButton,
|
||||
MobileStepper,
|
||||
IconButton,
|
||||
Typography,
|
||||
Stack,
|
||||
DialogActions,
|
||||
Button,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import { alpha } from "@mui/material/styles";
|
||||
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
|
||||
import BrandedBackground from "assets/BrandedBackground";
|
||||
import Logo from "assets/Logo";
|
||||
import ScrollableDialogContent from "components/Modal/ScrollableDialogContent";
|
||||
import { SlideTransition } from "components/Modal/SlideTransition";
|
||||
|
||||
import Step0Welcome from "@src/components/Setup/Step0Welcome";
|
||||
import Step1RowyRun from "@src/components/Setup/Step1RowyRun";
|
||||
|
||||
import { useAppContext } from "contexts/AppContext";
|
||||
import { name } from "@root/package.json";
|
||||
import routes from "constants/routes";
|
||||
|
||||
export interface ISetupStep {
|
||||
id: string;
|
||||
layout?: "centered" | "step";
|
||||
shortTitle: string;
|
||||
title: React.ReactNode;
|
||||
description: React.ReactNode;
|
||||
body: React.ReactNode;
|
||||
actions?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface ISetupStepBodyProps {
|
||||
completion: Record<string, boolean>;
|
||||
setCompletion: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
|
||||
checkAllSteps: () => void;
|
||||
rowyRunUrl: string;
|
||||
}
|
||||
|
||||
export default function SetupPage() {
|
||||
const { params } = useRouteMatch<{ step: string }>();
|
||||
const { projectId } = 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<Record<string, boolean>>({
|
||||
welcome: false,
|
||||
rowyRun: false,
|
||||
serviceAccount: false,
|
||||
signIn: false,
|
||||
rules: false,
|
||||
migrate: false,
|
||||
});
|
||||
|
||||
const checkAllSteps = () => {};
|
||||
|
||||
useEffect(() => {
|
||||
if (rowyRunUrl) checkAllSteps();
|
||||
}, [rowyRunUrl]);
|
||||
|
||||
const stepProps = { completion, setCompletion, checkAllSteps, rowyRunUrl };
|
||||
|
||||
const steps: ISetupStep[] = [
|
||||
{
|
||||
id: "welcome",
|
||||
layout: "centered",
|
||||
shortTitle: "Welcome",
|
||||
title: `Welcome to ${name}`,
|
||||
description: (
|
||||
<>
|
||||
<Typography variant="body1" gutterBottom>
|
||||
Get up and running in around 5 minutes.
|
||||
</Typography>
|
||||
<Typography variant="body1" paragraph>
|
||||
You’ll easily set up backend functionality, Firestore Rules, and
|
||||
user management.
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
You’ll set up the project: <b>{projectId}</b>
|
||||
</Typography>
|
||||
</>
|
||||
),
|
||||
body: <Step0Welcome {...stepProps} />,
|
||||
actions: completion.welcome ? (
|
||||
<Button variant="contained" color="primary" type="submit">
|
||||
Get Started
|
||||
</Button>
|
||||
) : (
|
||||
<Tooltip title="Please accept the terms and conditions">
|
||||
<div>
|
||||
<Button variant="contained" color="primary" disabled>
|
||||
Get Started
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "rowyRun",
|
||||
shortTitle: `${name} Run`,
|
||||
title: `Set Up ${name} Run`,
|
||||
description: `${name} Run is a Google Cloud Run instance that provides back-end functionality, such as table action scripts, user management, and easy Cloud Function deployment.`,
|
||||
body: <Step1RowyRun {...stepProps} />,
|
||||
},
|
||||
{
|
||||
id: "serviceAccount",
|
||||
shortTitle: `Service Account`,
|
||||
title: `Set Up Service Account`,
|
||||
description: `${name} Run is a Google Cloud Run instance that provides back-end functionality, such as table action scripts, user management, and easy Cloud Functions deployment. Learn more`,
|
||||
body: `x`,
|
||||
},
|
||||
{
|
||||
id: "signIn",
|
||||
shortTitle: `Sign In`,
|
||||
title: `Sign In as the Project Owner`,
|
||||
description: `${name} Run is a Google Cloud Run instance that provides back-end functionality, such as table action scripts, user management, and easy Cloud Functions deployment. Learn more`,
|
||||
body: `x`,
|
||||
},
|
||||
{
|
||||
id: "rules",
|
||||
shortTitle: `Rules`,
|
||||
title: `Set Up Firestore Rules`,
|
||||
description: `${name} Run is a Google Cloud Run instance that provides back-end functionality, such as table action scripts, user management, and easy Cloud Functions deployment. Learn more`,
|
||||
body: `x`,
|
||||
},
|
||||
{
|
||||
id: "migrate",
|
||||
shortTitle: `Migrate`,
|
||||
title: `Migrate to ${name}`,
|
||||
description: `${name} Run is a Google Cloud Run instance that provides back-end functionality, such as table action scripts, user management, and easy Cloud Functions deployment. Learn more`,
|
||||
body: `x`,
|
||||
},
|
||||
{
|
||||
id: "finish",
|
||||
layout: "centered",
|
||||
shortTitle: `Finish`,
|
||||
title: `You’re all set up!`,
|
||||
description: `You can now create a table from your Firestore collections or continue to ${name}.`,
|
||||
body: <div>x</div>,
|
||||
actions: (
|
||||
<>
|
||||
<Button variant="contained" color="primary">
|
||||
Create Table
|
||||
</Button>
|
||||
<Button component={Link} to={routes.home} sx={{ ml: 1 }}>
|
||||
Continue to {name}
|
||||
</Button>
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
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]) {
|
||||
console.log("iteration", steps[nextIncompleteStepIndex]?.id);
|
||||
nextIncompleteStepIndex++;
|
||||
}
|
||||
|
||||
setStepId(steps[nextIncompleteStepIndex].id);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<BrandedBackground />
|
||||
@@ -49,23 +210,16 @@ export default function SetupPage() {
|
||||
"& > *": { px: { xs: 2, sm: 4 } },
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
|
||||
"& .MuiTypography-inherit, & .MuiDialogContent-root": {
|
||||
typography: "body1",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{isMobile ? (
|
||||
<MobileStepper
|
||||
variant="dots"
|
||||
steps={4}
|
||||
backButton={null}
|
||||
nextButton={null}
|
||||
position="static"
|
||||
sx={{
|
||||
background: "none",
|
||||
m: 1,
|
||||
mt: 1.25,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
{stepId === "welcome" ? null : !isMobile ? (
|
||||
<Stepper
|
||||
activeStep={stepIndex - 1}
|
||||
nonLinear
|
||||
sx={{
|
||||
mt: 2.5,
|
||||
mb: 3,
|
||||
@@ -74,41 +228,152 @@ export default function SetupPage() {
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
<Step>
|
||||
<StepLabel>Rowy Run</StepLabel>
|
||||
</Step>
|
||||
<Step>
|
||||
<StepLabel>Service Account</StepLabel>
|
||||
</Step>
|
||||
<Step>
|
||||
<StepLabel>Sign In</StepLabel>
|
||||
</Step>
|
||||
<Step>
|
||||
<StepLabel>Firestore Rules</StepLabel>
|
||||
</Step>
|
||||
{listedSteps.map(({ id, shortTitle }, i) => (
|
||||
<Step key={id} completed={completion[id]}>
|
||||
<StepButton
|
||||
onClick={() => setStepId(id)}
|
||||
disabled={i > 0 && !completion[listedSteps[i - 1]?.id]}
|
||||
sx={{ py: 2, my: -2, borderRadius: 1 }}
|
||||
>
|
||||
{shortTitle}
|
||||
</StepButton>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
) : (
|
||||
<MobileStepper
|
||||
variant="dots"
|
||||
steps={listedSteps.length}
|
||||
activeStep={stepIndex - 1}
|
||||
backButton={
|
||||
<IconButton
|
||||
aria-label="Previous step"
|
||||
disabled={stepIndex === 0}
|
||||
onClick={() => setStepId(steps[stepIndex - 1].id)}
|
||||
>
|
||||
<ChevronLeftIcon />
|
||||
</IconButton>
|
||||
}
|
||||
nextButton={
|
||||
<IconButton
|
||||
aria-label="Next step"
|
||||
disabled={!completion[stepId]}
|
||||
onClick={() => setStepId(steps[stepIndex + 1].id)}
|
||||
>
|
||||
<ChevronRightIcon />
|
||||
</IconButton>
|
||||
}
|
||||
position="static"
|
||||
sx={{
|
||||
background: "none",
|
||||
p: 0,
|
||||
"& .MuiMobileStepper-dot": { mx: 0.5 },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Typography
|
||||
variant="h4"
|
||||
component="h1"
|
||||
sx={{ mb: 1, typography: { xs: "h5", md: "h4" } }}
|
||||
{step.layout === "centered" ? (
|
||||
<ScrollableDialogContent disableTopDivider>
|
||||
<Stack
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
spacing={3}
|
||||
sx={{
|
||||
minHeight: "100%",
|
||||
maxWidth: 400,
|
||||
margin: "0 auto",
|
||||
textAlign: "center",
|
||||
py: 3,
|
||||
}}
|
||||
>
|
||||
{stepId === "welcome" && (
|
||||
<SwitchTransition mode="out-in">
|
||||
<SlideTransition key={stepId} appear timeout={50}>
|
||||
<Logo size={2} />
|
||||
</SlideTransition>
|
||||
</SwitchTransition>
|
||||
)}
|
||||
|
||||
<SwitchTransition mode="out-in">
|
||||
<SlideTransition key={stepId} appear timeout={100}>
|
||||
<div>
|
||||
<Typography
|
||||
variant="h4"
|
||||
component="h1"
|
||||
sx={{ mb: 1, typography: { xs: "h5", md: "h4" } }}
|
||||
>
|
||||
{step.title}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="inherit" component="div">
|
||||
{step.description}
|
||||
</Typography>
|
||||
</div>
|
||||
</SlideTransition>
|
||||
</SwitchTransition>
|
||||
|
||||
<SwitchTransition mode="out-in">
|
||||
<SlideTransition key={stepId} appear timeout={150}>
|
||||
<Stack spacing={4} alignItems="center">
|
||||
{step.body}
|
||||
</Stack>
|
||||
</SlideTransition>
|
||||
</SwitchTransition>
|
||||
</Stack>
|
||||
</ScrollableDialogContent>
|
||||
) : (
|
||||
<>
|
||||
<SwitchTransition mode="out-in">
|
||||
<SlideTransition key={stepId} appear timeout={50}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
component="h1"
|
||||
sx={{ mb: 1, typography: { xs: "h5", md: "h4" } }}
|
||||
>
|
||||
{step.title}
|
||||
</Typography>
|
||||
</SlideTransition>
|
||||
</SwitchTransition>
|
||||
|
||||
<SwitchTransition mode="out-in">
|
||||
<SlideTransition key={stepId} appear timeout={100}>
|
||||
<ScrollableDialogContent disableTopDivider>
|
||||
<Stack spacing={4}>
|
||||
<Typography variant="body1" style={{ maxWidth: "70ch" }}>
|
||||
{step.description}
|
||||
</Typography>
|
||||
{step.body}
|
||||
</Stack>
|
||||
</ScrollableDialogContent>
|
||||
</SlideTransition>
|
||||
</SwitchTransition>
|
||||
</>
|
||||
)}
|
||||
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
handleContinue();
|
||||
} catch (e: any) {
|
||||
throw new Error(e.message);
|
||||
}
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
Set Up Rowy Run
|
||||
</Typography>
|
||||
|
||||
<ScrollableDialogContent>
|
||||
<Typography variant="body1">
|
||||
Rowy Run is a Cloud Run instance.
|
||||
</Typography>
|
||||
<div style={{ height: "100vh" }}>content</div>
|
||||
</ScrollableDialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button variant="contained" color="primary">
|
||||
Get Started
|
||||
</Button>
|
||||
</DialogActions>
|
||||
<DialogActions>
|
||||
{step.actions ?? (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={!completion[stepId]}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -70,7 +70,7 @@ export default function CheckboxIcon() {
|
||||
>
|
||||
<svg viewBox="0 0 18 18">
|
||||
<polyline
|
||||
stroke-width="2"
|
||||
strokeWidth="2"
|
||||
points="2.705 8.29 7 12.585 15.295 4.29"
|
||||
fill="none"
|
||||
className="tick"
|
||||
|
||||
@@ -69,7 +69,7 @@ export default function CheckboxIndeterminateIcon() {
|
||||
}}
|
||||
>
|
||||
<svg viewBox="0 0 18 18">
|
||||
<line x1="3" y1="9" x2="15" y2="9" stroke-width="2" className="tick" />
|
||||
<line x1="3" y1="9" x2="15" y2="9" strokeWidth="2" className="tick" />
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -810,14 +810,15 @@ export const components = (theme: Theme): ThemeOptions => {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: theme.palette.divider,
|
||||
"&.Mui-completed:not(.Mui-active)": {
|
||||
color: theme.palette.text.disabled,
|
||||
},
|
||||
},
|
||||
text: {
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
fill: theme.palette.text.secondary,
|
||||
|
||||
".Mui-active &": {
|
||||
fill: theme.palette.primary.contrastText,
|
||||
},
|
||||
".Mui-active &": { fill: theme.palette.primary.contrastText },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user