From 402103242b18bbbd3cca6f61aaf6196ae506d0ac Mon Sep 17 00:00:00 2001 From: Ryo Narita Date: Wed, 16 Dec 2020 15:04:53 +0900 Subject: [PATCH] Add ability to edit piped configuration --- pkg/app/web/src/api/piped.ts | 16 +++ ...ories.tsx => add-piped-drawer.stories.tsx} | 14 +- .../web/src/components/add-piped-drawer.tsx | 53 ++++++++ .../components/application-detail.stories.tsx | 1 + .../components/edit-piped-drawer.stories.tsx | 34 +++++ .../web/src/components/edit-piped-drawer.tsx | 63 +++++++++ .../{add-piped-form.tsx => piped-form.tsx} | 124 +++++++++-------- pkg/app/web/src/constants/toast-text.ts | 4 + pkg/app/web/src/modules/pipeds.test.ts | 87 +++++------- pkg/app/web/src/modules/pipeds.ts | 36 ++++- pkg/app/web/src/pages/settings/piped.tsx | 127 ++++++++---------- 11 files changed, 362 insertions(+), 197 deletions(-) rename pkg/app/web/src/components/{add-piped-form.stories.tsx => add-piped-drawer.stories.tsx} (71%) create mode 100644 pkg/app/web/src/components/add-piped-drawer.tsx create mode 100644 pkg/app/web/src/components/edit-piped-drawer.stories.tsx create mode 100644 pkg/app/web/src/components/edit-piped-drawer.tsx rename pkg/app/web/src/components/{add-piped-form.tsx => piped-form.tsx} (58%) diff --git a/pkg/app/web/src/api/piped.ts b/pkg/app/web/src/api/piped.ts index 5bca1501ec..509e087770 100644 --- a/pkg/app/web/src/api/piped.ts +++ b/pkg/app/web/src/api/piped.ts @@ -12,6 +12,8 @@ import { RecreatePipedKeyResponse, GenerateApplicationSealedSecretRequest, GenerateApplicationSealedSecretResponse, + UpdatePipedRequest, + UpdatePipedResponse, } from "pipe/pkg/app/web/api_client/service_pb"; export const getPipeds = ({ @@ -71,3 +73,17 @@ export const generateApplicationSealedSecret = ({ req.setData(data); return apiRequest(req, apiClient.generateApplicationSealedSecret); }; + +export const updatePiped = ({ + pipedId, + name, + desc, + envIdsList, +}: UpdatePipedRequest.AsObject): Promise => { + const req = new UpdatePipedRequest(); + req.setPipedId(pipedId); + req.setName(name); + req.setDesc(desc); + req.setEnvIdsList(envIdsList); + return apiRequest(req, apiClient.updatePiped); +}; diff --git a/pkg/app/web/src/components/add-piped-form.stories.tsx b/pkg/app/web/src/components/add-piped-drawer.stories.tsx similarity index 71% rename from pkg/app/web/src/components/add-piped-form.stories.tsx rename to pkg/app/web/src/components/add-piped-drawer.stories.tsx index e5dfc8fb6e..045b5a06fe 100644 --- a/pkg/app/web/src/components/add-piped-form.stories.tsx +++ b/pkg/app/web/src/components/add-piped-drawer.stories.tsx @@ -1,14 +1,14 @@ -import React from "react"; -import { AddPipedForm } from "./add-piped-form"; import { action } from "@storybook/addon-actions"; +import React from "react"; import { createDecoratorRedux } from "../../.storybook/redux-decorator"; import { dummyEnv } from "../__fixtures__/dummy-environment"; +import { AddPipedDrawer } from "./add-piped-drawer"; const env2 = { ...dummyEnv, id: "env-2", name: "development" }; export default { - title: "SETTINGS/AddPipedForm", - component: AddPipedForm, + title: "SETTINGS/Piped/AddPipedDrawer", + component: AddPipedDrawer, decorators: [ createDecoratorRedux({ environments: { @@ -23,9 +23,5 @@ export default { }; export const overview: React.FC = () => ( - + ); diff --git a/pkg/app/web/src/components/add-piped-drawer.tsx b/pkg/app/web/src/components/add-piped-drawer.tsx new file mode 100644 index 0000000000..056f57a287 --- /dev/null +++ b/pkg/app/web/src/components/add-piped-drawer.tsx @@ -0,0 +1,53 @@ +import React, { FC, memo, useCallback } from "react"; +import { Drawer } from "@material-ui/core"; +import { PipedForm, PipedFormValues, validationSchema } from "./piped-form"; +import { useFormik } from "formik"; +import { useDispatch, useSelector } from "react-redux"; +import { AppState } from "../modules"; +import { selectProjectName } from "../modules/me"; +import { addPiped } from "../modules/pipeds"; +import { AppDispatch } from "../store"; +import { addToast } from "../modules/toasts"; +import { ADD_PIPED_SUCCESS } from "../constants/toast-text"; + +interface Props { + open: boolean; + onClose: () => void; +} + +export const AddPipedDrawer: FC = memo(function AddPipedDrawer({ + open, + onClose, +}) { + const dispatch = useDispatch(); + const projectName = useSelector((state) => + selectProjectName(state.me) + ); + + const formik = useFormik({ + initialValues: { name: "", desc: "", envIds: [] }, + validationSchema, + validateOnMount: true, + async onSubmit(values) { + await dispatch(addPiped(values)).then(() => { + dispatch(addToast({ message: ADD_PIPED_SUCCESS, severity: "success" })); + onClose(); + }); + }, + }); + + const handleClose = useCallback(() => { + onClose(); + formik.resetForm(); + }, [formik, onClose]); + + return ( + + + + ); +}); diff --git a/pkg/app/web/src/components/application-detail.stories.tsx b/pkg/app/web/src/components/application-detail.stories.tsx index 626d3f7583..b06126a169 100644 --- a/pkg/app/web/src/components/application-detail.stories.tsx +++ b/pkg/app/web/src/components/application-detail.stories.tsx @@ -35,6 +35,7 @@ const dummyStore: Partial = { hasError: {}, }, pipeds: { + updating: false, entities: { [dummyPiped.id]: dummyPiped, }, diff --git a/pkg/app/web/src/components/edit-piped-drawer.stories.tsx b/pkg/app/web/src/components/edit-piped-drawer.stories.tsx new file mode 100644 index 0000000000..96c4dc5de3 --- /dev/null +++ b/pkg/app/web/src/components/edit-piped-drawer.stories.tsx @@ -0,0 +1,34 @@ +import { action } from "@storybook/addon-actions"; +import React from "react"; +import { createDecoratorRedux } from "../../.storybook/redux-decorator"; +import { dummyEnv } from "../__fixtures__/dummy-environment"; +import { dummyPiped } from "../__fixtures__/dummy-piped"; +import { EditPipedDrawer } from "./edit-piped-drawer"; + +const env2 = { ...dummyEnv, id: "env-2", name: "development" }; + +export default { + title: "SETTINGS/Piped/EditPipedDrawer", + component: EditPipedDrawer, + decorators: [ + createDecoratorRedux({ + pipeds: { + entities: { + [dummyPiped.id]: dummyPiped, + }, + ids: [dummyPiped.id], + }, + environments: { + entities: { + [dummyEnv.id]: dummyEnv, + [env2.id]: env2, + }, + ids: [dummyEnv.id, env2.id], + }, + }), + ], +}; + +export const overview: React.FC = () => ( + +); diff --git a/pkg/app/web/src/components/edit-piped-drawer.tsx b/pkg/app/web/src/components/edit-piped-drawer.tsx new file mode 100644 index 0000000000..af22a0b723 --- /dev/null +++ b/pkg/app/web/src/components/edit-piped-drawer.tsx @@ -0,0 +1,63 @@ +import React, { FC, memo, useCallback } from "react"; +import { Drawer } from "@material-ui/core"; +import { PipedForm, PipedFormValues, validationSchema } from "./piped-form"; +import { useFormik } from "formik"; +import { useDispatch, useSelector } from "react-redux"; +import { AppState } from "../modules"; +import { editPiped, fetchPipeds, Piped, selectById } from "../modules/pipeds"; +import { AppDispatch } from "../store"; +import { addToast } from "../modules/toasts"; +import { UPDATE_PIPED_SUCCESS } from "../constants/toast-text"; + +interface Props { + pipedId: string | null; + onClose: () => void; +} + +export const EditPipedDrawer: FC = memo(function EditPipedDrawer({ + pipedId, + onClose, +}) { + const dispatch = useDispatch(); + const piped = useSelector((state) => + pipedId ? selectById(state.pipeds, pipedId) : undefined + ); + + const formik = useFormik({ + initialValues: { + name: piped?.name || "", + desc: piped?.desc || "", + envIds: piped?.envIdsList || [], + }, + enableReinitialize: true, + validationSchema, + async onSubmit({ desc, envIds, name }) { + if (!pipedId) { + return; + } + + await dispatch(editPiped({ pipedId, name, desc, envIds })).then(() => { + dispatch(fetchPipeds(true)); + dispatch( + addToast({ message: UPDATE_PIPED_SUCCESS, severity: "success" }) + ); + onClose(); + }); + }, + }); + + const handleClose = useCallback(() => { + onClose(); + formik.resetForm(); + }, [formik, onClose]); + + return ( + + + + ); +}); diff --git a/pkg/app/web/src/components/add-piped-form.tsx b/pkg/app/web/src/components/piped-form.tsx similarity index 58% rename from pkg/app/web/src/components/add-piped-form.tsx rename to pkg/app/web/src/components/piped-form.tsx index 18e9e3d04c..459e8ad7df 100644 --- a/pkg/app/web/src/components/add-piped-form.tsx +++ b/pkg/app/web/src/components/piped-form.tsx @@ -1,24 +1,26 @@ -import React, { FC } from "react"; import { - makeStyles, - Divider, - Typography, - TextField, + Box, Button, Checkbox, - Box, + Divider, + makeStyles, + TextField, + Typography, } from "@material-ui/core"; +import CheckBoxIcon from "@material-ui/icons/CheckBox"; +import CheckBoxOutlineBlankIcon from "@material-ui/icons/CheckBoxOutlineBlank"; import Autocomplete from "@material-ui/lab/Autocomplete"; +import { Dictionary } from "@reduxjs/toolkit"; +import { FormikProps } from "formik"; +import React, { FC, memo } from "react"; +import { useSelector } from "react-redux"; +import * as Yup from "yup"; import { AppState } from "../modules"; import { Environment, - selectAll as selectAllEnvs, + selectEntities, + selectAll, } from "../modules/environments"; -import { useSelector } from "react-redux"; -import CheckBoxOutlineBlankIcon from "@material-ui/icons/CheckBoxOutlineBlank"; -import CheckBoxIcon from "@material-ui/icons/CheckBox"; -import { useFormik } from "formik"; -import * as Yup from "yup"; const useStyles = makeStyles((theme) => ({ title: { @@ -29,61 +31,60 @@ const useStyles = makeStyles((theme) => ({ }, })); -const validationSchema = Yup.object().shape({ +export const validationSchema = Yup.object().shape({ name: Yup.string().required(), desc: Yup.string().required(), - envs: Yup.array().required(), + envIds: Yup.array().required(), }); -interface Props { - projectName: string; - onSubmit: (props: { name: string; desc: string; envIds: string[] }) => void; - onClose: () => void; +export interface PipedFormValues { + name: string; + desc: string; + envIds: string[]; } -export const AddPipedForm: FC = ({ projectName, onSubmit, onClose }) => { - const classes = useStyles(); - const environments = useSelector((state) => - selectAllEnvs(state.environments) - ); +type Props = FormikProps & { + title: string; + onClose: () => void; +}; - const formik = useFormik<{ name: string; desc: string; envs: Environment[] }>( - { - initialValues: { - name: "", - desc: "", - envs: [], - }, - validationSchema, - validateOnMount: true, - onSubmit: (values) => { - onSubmit({ - name: values.name, - desc: values.desc, - envIds: values.envs.map((env) => env.id), - }); - }, - } - ); +export const PipedForm: FC = memo(function PipedForm({ + title, + onClose, + handleSubmit, + handleChange, + setFieldValue, + values, + isValid, + isSubmitting, +}) { + const classes = useStyles(); + const [envs, entities] = useSelector< + AppState, + [Environment[], Dictionary] + >((state) => [ + selectAll(state.environments), + selectEntities(state.environments), + ]); return ( - {`Add a new piped to "${projectName}" project`} + + {title} + -
+ = ({ projectName, onSubmit, onClose }) => { label="Description" variant="outlined" margin="dense" - onChange={formik.handleChange} - value={formik.values.desc} + onChange={handleChange} + value={values.desc} fullWidth required + disabled={isSubmitting} /> entities[id]) as Environment[]} onChange={(_, newValue) => { - formik.setFieldValue("envs", newValue); + setFieldValue( + "envIds", + newValue.map((env) => env.id) + ); }} getOptionLabel={(option) => option.name} + disabled={isSubmitting} renderOption={(option, { selected }) => ( - + <> } checkedIcon={} @@ -116,7 +122,7 @@ export const AddPipedForm: FC = ({ projectName, onSubmit, onClose }) => { color="primary" /> {option.name} - + )} renderInput={(params) => ( = ({ projectName, onSubmit, onClose }) => { - +
); -}; +}); diff --git a/pkg/app/web/src/constants/toast-text.ts b/pkg/app/web/src/constants/toast-text.ts index dd1a251a01..5240b72cc6 100644 --- a/pkg/app/web/src/constants/toast-text.ts +++ b/pkg/app/web/src/constants/toast-text.ts @@ -7,3 +7,7 @@ export const UPDATE_SSO_SUCCESS = "Successfully updated SSO configurations."; export const GENERATE_API_KEY_SUCCESS = "Successfully generated API Key."; export const DISABLE_API_KEY_SUCCESS = "Successfully disabled API Key."; export const COPY_API_KEY = "API Key copied to clipboard."; + +// Piped +export const ADD_PIPED_SUCCESS = "Successfully added Piped."; +export const UPDATE_PIPED_SUCCESS = "Successfully updated Piped."; diff --git a/pkg/app/web/src/modules/pipeds.test.ts b/pkg/app/web/src/modules/pipeds.test.ts index a3303607b2..1eaabf2b17 100644 --- a/pkg/app/web/src/modules/pipeds.test.ts +++ b/pkg/app/web/src/modules/pipeds.test.ts @@ -9,6 +9,13 @@ import { Piped, } from "./pipeds"; +const baseState = { + entities: {}, + ids: [], + registeredPiped: null, + updating: false, +}; + test("selectPipedsByEnv", () => { const disabledPiped: Piped = { ...dummyPiped, id: "piped-2", disabled: true }; expect(selectPipedsByEnv({ entities: {}, ids: [] }, "env-1")).toEqual([]); @@ -32,19 +39,14 @@ describe("pipedsSlice reducer", () => { pipedsSlice.reducer(undefined, { type: "TEST_ACTION", }) - ).toEqual({ - entities: {}, - ids: [], - registeredPiped: null, - }); + ).toEqual(baseState); }); it(`should handle ${clearRegisteredPipedInfo.type}`, () => { expect( pipedsSlice.reducer( { - entities: {}, - ids: [], + ...baseState, registeredPiped: { id: "piped-1", key: "piped-key", @@ -54,33 +56,21 @@ describe("pipedsSlice reducer", () => { type: clearRegisteredPipedInfo.type, } ) - ).toEqual({ - entities: {}, - ids: [], - registeredPiped: null, - }); + ).toEqual(baseState); }); describe("addPiped", () => { it(`should handle ${addPiped.fulfilled.type}`, () => { expect( - pipedsSlice.reducer( - { - entities: {}, - ids: [], - registeredPiped: null, + pipedsSlice.reducer(baseState, { + type: addPiped.fulfilled.type, + payload: { + id: "piped-1", + key: "piped-key", }, - { - type: addPiped.fulfilled.type, - payload: { - id: "piped-1", - key: "piped-key", - }, - } - ) + }) ).toEqual({ - entities: {}, - ids: [], + ...baseState, registeredPiped: { id: "piped-1", key: "piped-key", @@ -92,21 +82,14 @@ describe("pipedsSlice reducer", () => { describe("fetchPipeds", () => { it(`should handle ${fetchPipeds.fulfilled.type}`, () => { expect( - pipedsSlice.reducer( - { - entities: {}, - ids: [], - registeredPiped: null, - }, - { - type: fetchPipeds.fulfilled.type, - payload: [dummyPiped], - } - ) + pipedsSlice.reducer(baseState, { + type: fetchPipeds.fulfilled.type, + payload: [dummyPiped], + }) ).toEqual({ + ...baseState, entities: { [dummyPiped.id]: dummyPiped }, ids: [dummyPiped.id], - registeredPiped: null, }); }); }); @@ -114,25 +97,17 @@ describe("pipedsSlice reducer", () => { describe("recreatePipedKey", () => { it(`should handle ${recreatePipedKey.fulfilled.type}`, () => { expect( - pipedsSlice.reducer( - { - entities: {}, - ids: [], - registeredPiped: null, - }, - { - type: recreatePipedKey.fulfilled.type, - payload: "recreated-piped-key", - meta: { - arg: { - pipedId: "piped-1", - }, + pipedsSlice.reducer(baseState, { + type: recreatePipedKey.fulfilled.type, + payload: "recreated-piped-key", + meta: { + arg: { + pipedId: "piped-1", }, - } - ) + }, + }) ).toEqual({ - entities: {}, - ids: [], + ...baseState, registeredPiped: { id: "piped-1", key: "recreated-piped-key", diff --git a/pkg/app/web/src/modules/pipeds.ts b/pkg/app/web/src/modules/pipeds.ts index 345249fbd1..d06f12af0d 100644 --- a/pkg/app/web/src/modules/pipeds.ts +++ b/pkg/app/web/src/modules/pipeds.ts @@ -14,6 +14,8 @@ export interface RegisteredPiped { key: string; } +const MODULE_NAME = "pipeds"; + const pipedsAdapter = createEntityAdapter({}); export const { @@ -23,7 +25,7 @@ export const { } = pipedsAdapter.getSelectors(); export const fetchPipeds = createAsyncThunk( - "pipeds/fetchList", + `${MODULE_NAME}/fetchList`, async (withStatus: boolean) => { const { pipedsList } = await pipedsApi.getPipeds({ withStatus }); @@ -34,7 +36,7 @@ export const fetchPipeds = createAsyncThunk( export const addPiped = createAsyncThunk< RegisteredPiped, { name: string; desc: string; envIds: string[] } ->("pipeds/add", async (props) => { +>(`${MODULE_NAME}/add`, async (props) => { const res = await pipedsApi.registerPiped({ desc: props.desc, envIdsList: props.envIds, @@ -44,33 +46,46 @@ export const addPiped = createAsyncThunk< }); export const disablePiped = createAsyncThunk( - "pipeds/disable", + `${MODULE_NAME}/disable`, async ({ pipedId }) => { await pipedsApi.disablePiped({ pipedId }); } ); export const enablePiped = createAsyncThunk( - "pipeds/enable", + `${MODULE_NAME}/enable`, async ({ pipedId }) => { await pipedsApi.enablePiped({ pipedId }); } ); export const recreatePipedKey = createAsyncThunk( - "pipeds/recreateKey", + `${MODULE_NAME}/recreateKey`, async ({ pipedId }) => { const { key } = await pipedsApi.recreatePipedKey({ id: pipedId }); return key; } ); +export const editPiped = createAsyncThunk< + void, + { pipedId: string; name: string; desc: string; envIds: string[] } +>(`${MODULE_NAME}/edit`, async ({ pipedId, name, desc, envIds }) => { + await pipedsApi.updatePiped({ + pipedId, + name, + desc, + envIdsList: envIds, + }); +}); export const pipedsSlice = createSlice({ - name: "pipeds", + name: MODULE_NAME, initialState: pipedsAdapter.getInitialState<{ registeredPiped: RegisteredPiped | null; + updating: boolean; }>({ registeredPiped: null, + updating: false, }), reducers: { clearRegisteredPipedInfo(state) { @@ -91,6 +106,15 @@ export const pipedsSlice = createSlice({ id: action.meta.arg.pipedId, key: action.payload, }; + }) + .addCase(editPiped.pending, (state) => { + state.updating = true; + }) + .addCase(editPiped.rejected, (state) => { + state.updating = false; + }) + .addCase(editPiped.fulfilled, (state) => { + state.updating = false; }); }, }); diff --git a/pkg/app/web/src/pages/settings/piped.tsx b/pkg/app/web/src/pages/settings/piped.tsx index d83f29685c..d84fac5bb4 100644 --- a/pkg/app/web/src/pages/settings/piped.tsx +++ b/pkg/app/web/src/pages/settings/piped.tsx @@ -1,53 +1,47 @@ import { + Accordion, + AccordionDetails, + AccordionSummary, + Box, Button, - Divider, - Drawer, - Toolbar, Dialog, - DialogTitle, DialogContent, - TextField, - Box, + DialogTitle, + Divider, + IconButton, List, ListItem, + ListItemSecondaryAction, ListItemText, makeStyles, - ListItemSecondaryAction, - IconButton, Menu, MenuItem, + TextField, + Toolbar, Typography, - Accordion, - AccordionSummary, - AccordionDetails, } from "@material-ui/core"; import { Add as AddIcon, MoreVert as MoreVertIcon } from "@material-ui/icons"; -import React, { FC, memo, useState } from "react"; +import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; +import clsx from "clsx"; import dayjs from "dayjs"; -import { AddPipedForm } from "../../components/add-piped-form"; +import React, { FC, memo, useCallback, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; +import { AddPipedDrawer } from "../../components/add-piped-drawer"; +import { EditPipedDrawer } from "../../components/edit-piped-drawer"; +import { AppState } from "../../modules"; import { - addPiped, - RegisteredPiped, clearRegisteredPipedInfo, - Piped, - selectAll, - fetchPipeds, disablePiped, enablePiped, + fetchPipeds, + Piped, recreatePipedKey, + RegisteredPiped, + selectAll, } from "../../modules/pipeds"; -import { AppState } from "../../modules"; import { AppDispatch } from "../../store"; -import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; -import clsx from "clsx"; -import { selectProjectName } from "../../modules/me"; const useStyles = makeStyles((theme) => ({ - main: { - height: "100%", - overflow: "auto", - }, item: { backgroundColor: theme.palette.background.paper, }, @@ -94,34 +88,32 @@ export const SettingsPipedPage: FC = memo(function SettingsPipedPage() { const classes = useStyles(); const [isOpenForm, setIsOpenForm] = useState(false); const [actionTarget, setActionTarget] = useState(null); + const [editPipedId, setEditPipedId] = useState(null); const [anchorEl, setAnchorEl] = useState(null); const isOpenMenu = Boolean(anchorEl); const dispatch = useDispatch(); const [enabledPipeds, disabledPipeds] = usePipeds(); - const projectName = useSelector((state) => - selectProjectName(state.me) - ); const registeredPiped = useSelector( (state) => state.pipeds.registeredPiped ); - const handleMenuOpen = ( - event: React.MouseEvent, - piped: Piped - ): void => { - setActionTarget(piped); - setAnchorEl(event.currentTarget); - }; + const handleMenuOpen = useCallback( + (event: React.MouseEvent, piped: Piped): void => { + setActionTarget(piped); + setAnchorEl(event.currentTarget); + }, + [] + ); - const closeMenu = (): void => { + const closeMenu = useCallback(() => { setAnchorEl(null); setTimeout(() => { setActionTarget(null); }, 200); - }; + }, []); - const handleDisableClick = (): void => { + const handleDisableClick = useCallback(() => { closeMenu(); if (!actionTarget) { return; @@ -132,33 +124,34 @@ export const SettingsPipedPage: FC = memo(function SettingsPipedPage() { dispatch(act({ pipedId: actionTarget.id })).then(() => { dispatch(fetchPipeds(true)); }); - }; + }, [dispatch, actionTarget, closeMenu]); - const handleSubmit = (props: { - name: string; - desc: string; - envIds: string[]; - }): void => { - dispatch(addPiped(props)).then(() => { - setIsOpenForm(false); - }); - }; - - const handleClose = (): void => { + const handleClose = useCallback(() => { setIsOpenForm(false); - }; + }, []); - const handleClosePipedInfo = (): void => { + const handleClosePipedInfo = useCallback(() => { dispatch(clearRegisteredPipedInfo()); dispatch(fetchPipeds(true)); - }; + }, [dispatch]); - const handleRecreate = (): void => { + const handleRecreate = useCallback(() => { if (actionTarget) { dispatch(recreatePipedKey({ pipedId: actionTarget.id })); } closeMenu(); - }; + }, [dispatch, actionTarget, closeMenu]); + + const handleEdit = useCallback(() => { + if (actionTarget) { + setEditPipedId(actionTarget.id); + } + closeMenu(); + }, [actionTarget, closeMenu]); + + const handleEditClose = useCallback(() => { + setEditPipedId(null); + }, []); return ( <> @@ -173,7 +166,7 @@ export const SettingsPipedPage: FC = memo(function SettingsPipedPage() { -
+ {enabledPipeds.map((piped) => ( - {dayjs(piped.startedAt * 1000).fromNow()} + {dayjs(piped.startedAt * 1000).fromNow()} -
+ Enable ) : ( [ - - Disable + + Edit , Recreate Key , + + Disable + , ] )} - - - + + Piped registered