mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-21 23:43:15 +01:00
feat: workspaces crud (resolves #15)
This commit is contained in:
parent
f634839adb
commit
b3a317dc4d
@ -18,18 +18,18 @@ const ModalContent = ({children}) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const ModalFooter = ({confirmText, cancelText, handleSubmit, handleCancel, confirmDisabled}) => {
|
||||
const ModalFooter = ({confirmText, cancelText, handleSubmit, handleCancel, confirmDisabled, hideCancel}) => {
|
||||
confirmText = confirmText || 'Save';
|
||||
cancelText = cancelText || 'Cancel';
|
||||
|
||||
return (
|
||||
<div className="flex justify-end p-4 bruno-modal-footer">
|
||||
<span className="mr-2">
|
||||
<span className={hideCancel ? "hidden" : "mr-2"}>
|
||||
<button type="button" onClick={handleCancel} className="btn btn-md btn-close">
|
||||
{cancelText}
|
||||
</button>
|
||||
</span>
|
||||
<span className="">
|
||||
<span>
|
||||
<button type="submit" className="submit btn btn-md btn-secondary" disabled={confirmDisabled} onClick={handleSubmit} >
|
||||
{confirmText}
|
||||
</button>
|
||||
@ -46,7 +46,8 @@ const Modal = ({
|
||||
handleCancel,
|
||||
handleConfirm,
|
||||
children,
|
||||
confirmDisabled
|
||||
confirmDisabled,
|
||||
hideCancel
|
||||
}) => {
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const escFunction = (event) => {
|
||||
@ -84,6 +85,7 @@ const Modal = ({
|
||||
handleCancel={() => closeModal()}
|
||||
handleSubmit={handleConfirm}
|
||||
confirmDisabled={confirmDisabled}
|
||||
hideCancel={hideCancel}
|
||||
/>
|
||||
</div>
|
||||
<div className="bruno-modal-backdrop" onClick={() => closeModal()} />
|
||||
|
8
renderer/components/Portal/index.js
Normal file
8
renderer/components/Portal/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
function Portal({ children, wrapperId }) {
|
||||
wrapperId = wrapperId || "bruno-app-body";
|
||||
|
||||
return createPortal(children, document.getElementById(wrapperId));
|
||||
}
|
||||
export default Portal;
|
@ -0,0 +1,70 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import Portal from "components/Portal/index";
|
||||
import Modal from "components/Modal/index";
|
||||
import { useFormik } from 'formik';
|
||||
import { addWorkspace } from 'providers/ReduxStore/slices/workspaces';
|
||||
import * as Yup from 'yup';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
const AddWorkspace = ({onClose}) => {
|
||||
const dispatch = useDispatch();
|
||||
const inputRef = useRef();
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
initialValues: {
|
||||
name: ""
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
name: Yup.string()
|
||||
.min(1, 'must be atleast 1 characters')
|
||||
.max(30, 'must be 30 characters or less')
|
||||
.required('name is required')
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
dispatch(addWorkspace({name: values.name}));
|
||||
onClose();
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if(inputRef && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [inputRef]);
|
||||
|
||||
const onSubmit = () => {
|
||||
formik.handleSubmit();
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal
|
||||
size="sm"
|
||||
title={"Add Workspace"}
|
||||
confirmText='Add'
|
||||
handleConfirm={onSubmit}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">Workpsace Name</label>
|
||||
<input
|
||||
id="workspace-name" type="text" name="name"
|
||||
ref={inputRef}
|
||||
className="block textbox mt-2 w-full"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.name || ''}
|
||||
/>
|
||||
{formik.touched.name && formik.errors.name ? (
|
||||
<div className="text-red-500">{formik.errors.name}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddWorkspace;
|
||||
|
@ -0,0 +1,15 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
button.submit {
|
||||
color: white;
|
||||
background-color: var(--color-background-danger) !important;
|
||||
border: inherit !important;
|
||||
|
||||
&:hover {
|
||||
border: inherit !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import Portal from "components/Portal/index";
|
||||
import Modal from "components/Modal/index";
|
||||
import { deleteWorkspace } from 'providers/ReduxStore/slices/workspaces';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const DeleteWorkspace = ({onClose, workspace}) => {
|
||||
const dispatch = useDispatch();
|
||||
const onConfirm = () =>{
|
||||
dispatch(deleteWorkspace({workspaceUid: workspace.uid}))
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<StyledWrapper>
|
||||
<Modal
|
||||
size="sm"
|
||||
title={"Delete Workspace"}
|
||||
confirmText="Delete"
|
||||
handleConfirm={onConfirm}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
Are you sure you want to delete <span className="font-semibold">{workspace.name}</span> ?
|
||||
</Modal>
|
||||
</StyledWrapper>
|
||||
</Portal>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default DeleteWorkspace;
|
||||
|
@ -0,0 +1,70 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import Portal from "components/Portal/index";
|
||||
import Modal from "components/Modal/index";
|
||||
import { useFormik } from 'formik';
|
||||
import { renameWorkspace } from 'providers/ReduxStore/slices/workspaces';
|
||||
import * as Yup from 'yup';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
const EditWorkspace = ({onClose, workspace}) => {
|
||||
const dispatch = useDispatch();
|
||||
const inputRef = useRef();
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
initialValues: {
|
||||
name: workspace.name
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
name: Yup.string()
|
||||
.min(1, 'must be atleast 1 characters')
|
||||
.max(30, 'must be 30 characters or less')
|
||||
.required('name is required')
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
dispatch(renameWorkspace({name: values.name, uid: workspace.uid}));
|
||||
onClose();
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if(inputRef && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [inputRef]);
|
||||
|
||||
const onSubmit = () => {
|
||||
formik.handleSubmit();
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal
|
||||
size="sm"
|
||||
title={"Rename Workspace"}
|
||||
confirmText='Rename'
|
||||
handleConfirm={onSubmit}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">Workpsace Name</label>
|
||||
<input
|
||||
id="workspace-name" type="text" name="name"
|
||||
ref={inputRef}
|
||||
className="block textbox mt-2 w-full"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.name || ''}
|
||||
/>
|
||||
{formik.touched.name && formik.errors.name ? (
|
||||
<div className="text-red-500">{formik.errors.name}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditWorkspace;
|
||||
|
@ -0,0 +1,17 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
div {
|
||||
padding: 4px 6px;
|
||||
padding-left: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
div:hover {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
@ -0,0 +1,27 @@
|
||||
import React, { useState } from "react";
|
||||
import EditModal from "../EditModal";
|
||||
import DeleteModal from "../DeleteModal";
|
||||
import { IconEdit, IconTrash } from "@tabler/icons";
|
||||
import StyledWrapper from "./StyledWrapper";
|
||||
|
||||
const WorkspaceItem = ({workspace}) => {
|
||||
const [openEditModal, setOpenEditModal] = useState(false);
|
||||
const [openDeleteModal, setOpenDeleteModal] = useState(false);
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="flex justify-between items-baseline mb-2" key={workspace.uid} >
|
||||
<li>{workspace.name}</li>
|
||||
<div className="flex gap-x-4" >
|
||||
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenEditModal(true)}/>
|
||||
<IconTrash className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenDeleteModal(true)}/>
|
||||
</div>
|
||||
{openEditModal && <EditModal onClose={() => setOpenEditModal(false)} workspace={workspace} />}
|
||||
{openDeleteModal && <DeleteModal onClose={() => setOpenDeleteModal(false)} workspace={workspace} />}
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default WorkspaceItem;
|
@ -1,36 +1,28 @@
|
||||
import Modal from "components/Modal/index";
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import WorkspaceItem from "./WorkspaceItem/index";
|
||||
import AddModal from "./AddModal";
|
||||
|
||||
const WorkspaceConfigurer = ({onClose}) => {
|
||||
const { workspaces } = useSelector((state) => state.workspaces);
|
||||
|
||||
const onSubmit = () => {
|
||||
onClose();
|
||||
}
|
||||
const [openAddModal, setOpenAddModal] = useState(false);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="md"
|
||||
title="Workspaces"
|
||||
confirmText="Create"
|
||||
handleConfirm={onSubmit}
|
||||
confirmText={"+ New Workspace"}
|
||||
handleConfirm={() => setOpenAddModal(true)}
|
||||
handleCancel={onClose}
|
||||
hideCancel={true}
|
||||
>
|
||||
<ul className="mb-2">
|
||||
{workspaces && workspaces.length && workspaces.map((workspace) => (
|
||||
<div className="flex justify-between items-baseline w-4/5 mb-2">
|
||||
<li key={workspace.uid}>{workspace.name}</li>
|
||||
<button
|
||||
style={{backgroundColor: "var(--color-brand)"}}
|
||||
className="flex items-center h-full text-white active:bg-blue-600 font-bold text-xs px-4 py-2 ml-2 uppercase rounded shadow hover:shadow-md outline-none focus:outline-none ease-linear transition-all duration-150"
|
||||
onClick={() => console.log("delete")}
|
||||
>
|
||||
<span style={{marginLeft: 5}}>Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<ul className="mb-2" >
|
||||
{workspaces && workspaces.length && workspaces.map((workspace) => (
|
||||
<WorkspaceItem workspace={workspace} />
|
||||
))}
|
||||
</ul>
|
||||
{openAddModal && <AddModal onClose={() => setOpenAddModal(false)}/>}
|
||||
</Modal>
|
||||
)
|
||||
|
||||
|
@ -34,7 +34,7 @@ export default class MyDocument extends Document {
|
||||
<Head>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"/>
|
||||
</Head>
|
||||
<body>
|
||||
<body id="bruno-app-body">
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { uuid } from 'utils/common';
|
||||
|
||||
const initialState = {
|
||||
workspaces: [{
|
||||
@ -21,11 +22,33 @@ export const workspacesSlice = createSlice({
|
||||
selectWorkspace: (state, action) => {
|
||||
state.activeWorkspaceUid = action.payload.uid;
|
||||
},
|
||||
renameWorkspace: (state, action) => {
|
||||
const { name, uid } = action.payload;
|
||||
const { workspaces } = state;
|
||||
const workspaceIndex = workspaces.findIndex(workspace => workspace.uid == uid);
|
||||
workspaces[workspaceIndex].name = name;
|
||||
},
|
||||
deleteWorkspace: (state, action) => {
|
||||
if(state.activeWorkspaceUid === action.payload.workspaceUid) {
|
||||
throw new Error("User cannot delete current workspace");
|
||||
}
|
||||
state.workspaces = state.workspaces.filter((workspace) => workspace.uid !== action.payload.workspaceUid);
|
||||
},
|
||||
addWorkspace: (state, action) => {
|
||||
const newWorkspace = {
|
||||
uid: uuid(),
|
||||
name: action.payload.name
|
||||
}
|
||||
state.workspaces.push(newWorkspace);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const {
|
||||
selectWorkspace
|
||||
selectWorkspace,
|
||||
renameWorkspace,
|
||||
deleteWorkspace,
|
||||
addWorkspace
|
||||
} = workspacesSlice.actions;
|
||||
|
||||
export default workspacesSlice.reducer;
|
||||
|
Loading…
Reference in New Issue
Block a user