github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/webui/src/lib/components/auth/forms.jsx (about) 1 import React, {useEffect, useRef, useState} from "react"; 2 import Modal from "react-bootstrap/Modal"; 3 import Badge from "react-bootstrap/Badge"; 4 import Form from "react-bootstrap/Form"; 5 import Button from "react-bootstrap/Button"; 6 import {FormControl, InputGroup} from "react-bootstrap"; 7 import {SearchIcon} from "@primer/octicons-react"; 8 9 import {useAPI} from "../../hooks/api"; 10 import {Checkbox, DataTable, DebouncedFormControl, AlertError, Loading} from "../controls"; 11 12 const resolveEntityDisplayName = (ent) => { 13 // for users 14 if (ent?.email?.length) return ent.email; 15 // for groups 16 if (ent?.name?.length) return ent.name; 17 return ent.id; 18 } 19 20 export const AttachModal = ({ show, searchFn, onAttach, onHide, addText = "Add", 21 emptyState = 'No matches', modalTitle = 'Add', headers = ['Select', 'ID'], 22 filterPlaceholder = 'Filter...'}) => { 23 const search = useRef(null); 24 const [searchPrefix, setSearchPrefix] = useState(""); 25 const [selected, setSelected] = useState([]); 26 27 useEffect(() => { 28 if (!!search.current && search.current.value === "") 29 search.current.focus(); 30 }); 31 32 const { response, error, loading } = useAPI(() => { 33 return searchFn(searchPrefix); 34 }, [searchPrefix]); 35 36 let content; 37 if (loading) content = <Loading/>; 38 else if (error) content = <AlertError error={error}/>; 39 else content = ( 40 <> 41 <DataTable 42 headers={headers} 43 keyFn={ent => ent.id} 44 emptyState={emptyState} 45 results={response} 46 rowFn={ent => [ 47 <Checkbox 48 defaultChecked={selected.indexOf(ent.id) >= 0} 49 onAdd={() => setSelected([...selected, ent])} 50 onRemove={() => setSelected(selected.filter(selectedEnt => selectedEnt.id !== ent.id))} 51 name={'selected'}/>, 52 <strong>{resolveEntityDisplayName(ent)}</strong> 53 ]}/> 54 55 <div className="mt-3"> 56 {(selected.length > 0) && 57 <p> 58 <strong>Selected: </strong> 59 {(selected.map(item => ( 60 <Badge key={item.id} pill variant="primary" className="me-1"> 61 {resolveEntityDisplayName(item)} 62 </Badge> 63 )))} 64 </p> 65 } 66 </div> 67 </> 68 ); 69 70 return ( 71 <Modal show={show} onHide={onHide}> 72 <Modal.Header closeButton> 73 <Modal.Title>{modalTitle}</Modal.Title> 74 </Modal.Header> 75 <Modal.Body> 76 <Form onSubmit={e => { e.preventDefault() }}> 77 <InputGroup> 78 <InputGroup.Text> 79 <SearchIcon/> 80 </InputGroup.Text> 81 <DebouncedFormControl 82 ref={search} 83 placeholder={filterPlaceholder} 84 onChange={() => {setSearchPrefix(search.current.value)}}/> 85 </InputGroup> 86 </Form> 87 <div className="mt-2"> 88 {content} 89 </div> 90 </Modal.Body> 91 <Modal.Footer> 92 <Button variant="success" disabled={selected.length === 0} onClick={() => {onAttach(selected)}}> 93 {addText} 94 </Button> 95 <Button variant="secondary" onClick={onHide}>Cancel</Button> 96 </Modal.Footer> 97 </Modal> 98 ); 99 }; 100 101 export const EntityActionModal = ({ show, onHide, onAction, title, placeholder, actionName, validationFunction = null }) => { 102 const [error, setError] = useState(null); 103 const idField = useRef(null); 104 105 useEffect(() => { 106 if (!!idField.current && idField.current.value === "") 107 idField.current.focus(); 108 }); 109 110 const onSubmit = () => { 111 if (validationFunction) { 112 const validationResult = validationFunction(idField.current.value); 113 if (!validationResult.isValid) { 114 setError(validationResult.errorMessage); 115 return; 116 } 117 } 118 onAction(idField.current.value).catch(err => setError(err)); 119 }; 120 121 return ( 122 <Modal show={show} onHide={onHide}> 123 <Modal.Header closeButton> 124 <Modal.Title>{title}</Modal.Title> 125 </Modal.Header> 126 127 <Modal.Body> 128 <Form onSubmit={e => { 129 e.preventDefault() 130 onSubmit() 131 }}> 132 <FormControl ref={idField} autoFocus placeholder={placeholder} type="text"/> 133 </Form> 134 135 {(!!error) && <AlertError className="mt-3" error={error}/>} 136 137 </Modal.Body> 138 139 <Modal.Footer> 140 <Button onClick={onSubmit} variant="success">{actionName}</Button> 141 <Button onClick={onHide} variant="secondary">Cancel</Button> 142 </Modal.Footer> 143 </Modal> 144 ); 145 };