diff --git a/src/App.tsx b/src/App.tsx
index 653b6a88e..9612ff03b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -41,6 +41,12 @@ const SetupPage = lazy(() => import("@src/pages/SetupPage" /* webpackChunkName:
const Navigation = lazy(() => import("@src/layouts/Navigation" /* webpackChunkName: "Navigation" */));
// prettier-ignore
const TableSettingsDialog = lazy(() => import("@src/components/TableSettingsDialog" /* webpackChunkName: "TableSettingsDialog" */));
+const ProjectSettingsDialog = lazy(
+ () =>
+ import(
+ "@src/components/ProjectSettingsDialog" /* webpackChunkName: "ProjectSettingsDialog" */
+ )
+);
// prettier-ignore
const TablesPage = lazy(() => import("@src/pages/TablesPage" /* webpackChunkName: "TablesPage" */));
@@ -99,6 +105,7 @@ export default function App() {
+
}
diff --git a/src/atoms/projectScope/ui.ts b/src/atoms/projectScope/ui.ts
index 48af64b02..01fc14996 100644
--- a/src/atoms/projectScope/ui.ts
+++ b/src/atoms/projectScope/ui.ts
@@ -131,6 +131,26 @@ export const tableSettingsDialogAtom = atom(
}
);
+export type ProjectSettingsDialogTab =
+ | "general"
+ | "rowy-run"
+ | "services"
+ | "secrets";
+export type ProjectSettingsDialogState = {
+ open: boolean;
+ tab: ProjectSettingsDialogTab;
+};
+export const projectSettingsDialogAtom = atom(
+ { open: false, tab: "secrets" } as ProjectSettingsDialogState,
+ (_, set, update?: Partial) => {
+ set(projectSettingsDialogAtom, {
+ open: true,
+ tab: "secrets",
+ ...update,
+ });
+ }
+);
+
/**
* Store the current ID of the table being edited in tableSettingsDialog
* to derive tableSettingsDialogSchemaAtom
diff --git a/src/components/ProjectSettingsDialog/ProjectSettingsDialog.tsx b/src/components/ProjectSettingsDialog/ProjectSettingsDialog.tsx
new file mode 100644
index 000000000..49e9c1c8b
--- /dev/null
+++ b/src/components/ProjectSettingsDialog/ProjectSettingsDialog.tsx
@@ -0,0 +1,282 @@
+import React from "react";
+import { useAtom } from "jotai";
+import {
+ projectScope,
+ projectSettingsDialogAtom,
+ ProjectSettingsDialogTab,
+ rowyRunAtom,
+ secretNamesAtom,
+ updateSecretNamesAtom,
+} from "@src/atoms/projectScope";
+import Modal from "@src/components/Modal";
+import { Box, Button, Paper, Tab, Tooltip, Typography } from "@mui/material";
+import { TabContext, TabPanel, TabList } from "@mui/lab";
+import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
+import EditIcon from "@mui/icons-material/Edit";
+import SecretDetailsModal from "./SecretDetailsModal";
+import { runRoutes } from "@src/constants/runRoutes";
+
+export default function ProjectSettingsDialog() {
+ const [{ open, tab }, setProjectSettingsDialog] = useAtom(
+ projectSettingsDialogAtom,
+ projectScope
+ );
+ const [secretNames] = useAtom(secretNamesAtom, projectScope);
+ const [secretDetailsModal, setSecretDetailsModal] = React.useState<{
+ open: boolean;
+ loading?: boolean;
+ mode?: "add" | "edit" | "delete";
+ secretName?: string;
+ error?: string;
+ }>({
+ open: false,
+ });
+ const [rowyRun] = useAtom(rowyRunAtom, projectScope);
+ const [updateSecretNames] = useAtom(updateSecretNamesAtom, projectScope);
+
+ if (!open) return null;
+
+ const handleClose = () => {
+ setProjectSettingsDialog({ open: false });
+ };
+
+ const handleTabChange = (
+ event: React.SyntheticEvent,
+ newTab: ProjectSettingsDialogTab
+ ) => {
+ setProjectSettingsDialog({ tab: newTab });
+ };
+
+ console.log(secretDetailsModal);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ Secrets
+
+
+
+ {secretNames.secretNames?.map((secretName) => (
+
+
+ {secretName}
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+ >
+ }
+ />
+ {
+ setSecretDetailsModal({ ...secretDetailsModal, open: false });
+ }}
+ handleAdd={async (newSecretName, secretValue) => {
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ loading: true,
+ });
+ try {
+ await rowyRun({
+ route: runRoutes.addSecret,
+ body: {
+ name: newSecretName,
+ value: secretValue,
+ },
+ });
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ open: false,
+ loading: false,
+ });
+ // update secret name causes an unknown modal-related bug, to be fixed
+ // updateSecretNames?.();
+ } catch (error: any) {
+ console.error(error);
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ error: error.message,
+ });
+ }
+ }}
+ handleEdit={async (secretName, secretValue) => {
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ loading: true,
+ });
+ try {
+ await rowyRun({
+ route: runRoutes.editSecret,
+ body: {
+ name: secretName,
+ value: secretValue,
+ },
+ });
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ open: false,
+ loading: false,
+ });
+ // update secret name causes an unknown modal-related bug, to be fixed
+ // updateSecretNames?.();
+ } catch (error: any) {
+ console.error(error);
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ error: error.message,
+ });
+ }
+ }}
+ handleDelete={async (secretName) => {
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ loading: true,
+ });
+ try {
+ await rowyRun({
+ route: runRoutes.deleteSecret,
+ body: {
+ name: secretName,
+ },
+ });
+ console.log("Setting", {
+ ...secretDetailsModal,
+ open: false,
+ loading: false,
+ });
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ open: false,
+ loading: false,
+ });
+ // update secret name causes an unknown modal-related bug, to be fixed
+ // updateSecretNames?.();
+ } catch (error: any) {
+ console.error(error);
+ setSecretDetailsModal({
+ ...secretDetailsModal,
+ error: error.message,
+ });
+ }
+ }}
+ />
+ >
+ );
+}
diff --git a/src/components/ProjectSettingsDialog/SecretDetailsModal.tsx b/src/components/ProjectSettingsDialog/SecretDetailsModal.tsx
new file mode 100644
index 000000000..1e156429d
--- /dev/null
+++ b/src/components/ProjectSettingsDialog/SecretDetailsModal.tsx
@@ -0,0 +1,157 @@
+import React, { useState } from "react";
+import Modal from "@src/components/Modal";
+import { Box, Button, TextField, Typography } from "@mui/material";
+import { capitalize } from "lodash-es";
+import LoadingButton from "@mui/lab/LoadingButton";
+
+export interface ISecretDetailsModalProps {
+ open: boolean;
+ loading?: boolean;
+ mode?: "add" | "edit" | "delete";
+ error?: string;
+ secretName?: string;
+ handleClose: () => void;
+ handleAdd: (secretName: string, secretValue: string) => void;
+ handleEdit: (secretName: string, secretValue: string) => void;
+ handleDelete: (secretName: string) => void;
+}
+
+export default function SecretDetailsModal({
+ open,
+ loading,
+ mode,
+ error,
+ secretName,
+ handleClose,
+ handleAdd,
+ handleEdit,
+ handleDelete,
+}: ISecretDetailsModalProps) {
+ const [newSecretName, setNewSecretName] = useState("");
+ const [secretValue, setSecretValue] = useState("");
+
+ return (
+
+ {mode === "add" && (
+
+ Secret Name
+ setNewSecretName(e.target.value)}
+ />
+
+ This will create a secret key on Google Cloud.
+
+
+ )}
+ {mode === "delete" ? (
+
+ Are you sure you want to delete this secret key {secretName}?
+
+ ) : (
+
+ Secret Value
+ setSecretValue(e.target.value)}
+ />
+
+ Paste your secret key here.
+
+
+ )}
+ {error?.length && (
+
+ {error}
+
+ )}
+
+
+ {
+ switch (mode) {
+ case "add":
+ handleAdd(newSecretName, secretValue);
+ break;
+ case "edit":
+ handleEdit(secretName ?? "", secretValue);
+ break;
+ case "delete":
+ handleDelete(secretName ?? "");
+ break;
+ }
+ }}
+ >
+ {mode === "delete" ? "Delete" : "Save"}
+
+
+
+ }
+ />
+ );
+}
diff --git a/src/components/ProjectSettingsDialog/index.ts b/src/components/ProjectSettingsDialog/index.ts
new file mode 100644
index 000000000..e57386ddc
--- /dev/null
+++ b/src/components/ProjectSettingsDialog/index.ts
@@ -0,0 +1,2 @@
+export * from "./ProjectSettingsDialog";
+export { default } from "./ProjectSettingsDialog";
diff --git a/src/components/TableModals/WebhooksModal/Schemas/stripe.tsx b/src/components/TableModals/WebhooksModal/Schemas/stripe.tsx
index 9c34383f6..0f146e7b0 100644
--- a/src/components/TableModals/WebhooksModal/Schemas/stripe.tsx
+++ b/src/components/TableModals/WebhooksModal/Schemas/stripe.tsx
@@ -7,6 +7,7 @@ import {
projectScope,
secretNamesAtom,
updateSecretNamesAtom,
+ projectSettingsDialogAtom,
} from "@src/atoms/projectScope";
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
@@ -56,6 +57,10 @@ export const webhookStripe = {
Auth: (webhookObject: IWebhook, setWebhookObject: (w: IWebhook) => void) => {
const [secretNames] = useAtom(secretNamesAtom, projectScope);
const [updateSecretNames] = useAtom(updateSecretNamesAtom, projectScope);
+ const [{ open, tab }, setProjectSettingsDialog] = useAtom(
+ projectSettingsDialogAtom,
+ projectScope
+ );
return (
<>
@@ -118,8 +123,9 @@ export const webhookStripe = {
})}