1- import React , { forwardRef , useEffect , useRef } from 'react' ;
1+ import React , { forwardRef , memo , useCallback , useEffect , useRef } from 'react' ;
22import { Subuser } from '@/state/server/subusers' ;
33import { Form , Formik , FormikHelpers , useFormikContext } from 'formik' ;
44import { array , object , string } from 'yup' ;
@@ -11,7 +11,6 @@ import Checkbox from '@/components/elements/Checkbox';
1111import styled from 'styled-components/macro' ;
1212import createOrUpdateSubuser from '@/api/server/users/createOrUpdateSubuser' ;
1313import { ServerContext } from '@/state/server' ;
14- import { httpErrorToHuman } from '@/api/http' ;
1514import FlashMessageRender from '@/components/FlashMessageRender' ;
1615import Can from '@/components/elements/Can' ;
1716import { usePermissions } from '@/plugins/usePermissions' ;
@@ -20,6 +19,7 @@ import tw from 'twin.macro';
2019import Button from '@/components/elements/Button' ;
2120import Label from '@/components/elements/Label' ;
2221import Input from '@/components/elements/Input' ;
22+ import isEqual from 'react-fast-compare' ;
2323
2424type Props = {
2525 subuser ?: Subuser ;
@@ -31,7 +31,7 @@ interface Values {
3131}
3232
3333const PermissionLabel = styled . label `
34- ${ tw `flex items-center border border-transparent rounded md:p-2` } ;
34+ ${ tw `flex items-center border border-transparent rounded md:p-2 transition-colors duration-75 ` } ;
3535 text-transform: none;
3636
3737 &:not(.disabled) {
@@ -41,6 +41,10 @@ const PermissionLabel = styled.label`
4141 ${ tw `border-neutral-500 bg-neutral-800` } ;
4242 }
4343 }
44+
45+ &:not(:first-of-type) {
46+ ${ tw `mt-4 sm:mt-2` } ;
47+ }
4448
4549 &.disabled {
4650 ${ tw `opacity-50` } ;
@@ -51,8 +55,58 @@ const PermissionLabel = styled.label`
5155 }
5256` ;
5357
58+ interface TitleProps {
59+ isEditable : boolean ;
60+ permission : string ;
61+ permissions : string [ ] ;
62+ children : React . ReactNode ;
63+ className ?: string ;
64+ }
65+
66+ const PermissionTitledBox = memo ( ( { isEditable, permission, permissions, className, children } : TitleProps ) => {
67+ const { values, setFieldValue } = useFormikContext < Values > ( ) ;
68+
69+ const onCheckboxClicked = useCallback ( ( e : React . ChangeEvent < HTMLInputElement > ) => {
70+ console . log ( e . currentTarget . checked , [
71+ ...values . permissions ,
72+ ...permissions . filter ( p => ! values . permissions . includes ( p ) ) ,
73+ ] ) ;
74+
75+ if ( e . currentTarget . checked ) {
76+ setFieldValue ( 'permissions' , [
77+ ...values . permissions ,
78+ ...permissions . filter ( p => ! values . permissions . includes ( p ) ) ,
79+ ] ) ;
80+ } else {
81+ setFieldValue ( 'permissions' , [
82+ ...values . permissions . filter ( p => ! permissions . includes ( p ) ) ,
83+ ] ) ;
84+ }
85+ } , [ permissions , values . permissions ] ) ;
86+
87+ return (
88+ < TitledGreyBox
89+ title = {
90+ < div css = { tw `flex items-center` } >
91+ < p css = { tw `text-sm uppercase flex-1` } > { permission } </ p >
92+ { isEditable &&
93+ < Input
94+ type = { 'checkbox' }
95+ checked = { permissions . every ( p => values . permissions . includes ( p ) ) }
96+ onChange = { onCheckboxClicked }
97+ />
98+ }
99+ </ div >
100+ }
101+ className = { className }
102+ >
103+ { children }
104+ </ TitledGreyBox >
105+ ) ;
106+ } , isEqual ) ;
107+
54108const EditSubuserModal = forwardRef < HTMLHeadingElement , Props > ( ( { subuser, ...props } , ref ) => {
55- const { values , isSubmitting, setFieldValue } = useFormikContext < Values > ( ) ;
109+ const { isSubmitting } = useFormikContext < Values > ( ) ;
56110 const [ canEditUser ] = usePermissions ( subuser ? [ 'user.update' ] : [ 'user.create' ] ) ;
57111 const permissions = useStoreState ( state => state . permissions . data ) ;
58112
@@ -104,73 +158,48 @@ const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...pr
104158 </ div >
105159 }
106160 < div css = { tw `my-6` } >
107- { Object . keys ( permissions ) . filter ( key => key !== 'websocket' ) . map ( ( key , index ) => (
108- < TitledGreyBox
109- key = { key }
110- title = {
111- < div css = { tw `flex items-center` } >
112- < p css = { tw `text-sm uppercase flex-1` } > { key } </ p >
113- { canEditUser &&
114- < Input
115- type = { 'checkbox' }
116- onClick = { e => {
117- if ( e . currentTarget . checked ) {
118- setFieldValue ( 'permissions' , [
119- ...values . permissions ,
120- ...Object . keys ( permissions [ key ] . keys )
121- . map ( pkey => `${ key } .${ pkey } ` )
122- . filter ( permission => values . permissions . indexOf ( permission ) === - 1 ) ,
123- ] ) ;
124- } else {
125- setFieldValue ( 'permissions' , [
126- ...values . permissions . filter (
127- permission => Object . keys ( permissions [ key ] . keys )
128- . map ( pkey => `${ key } .${ pkey } ` )
129- . indexOf ( permission ) < 0 ,
130- ) ,
131- ] ) ;
161+ { Object . keys ( permissions ) . filter ( key => key !== 'websocket' ) . map ( ( key , index ) => {
162+ const group = Object . keys ( permissions [ key ] . keys ) . map ( pkey => `${ key } .${ pkey } ` ) ;
163+
164+ return (
165+ < PermissionTitledBox
166+ key = { `permission_${ key } ` }
167+ isEditable = { canEditUser }
168+ permission = { key }
169+ permissions = { group }
170+ css = { index > 0 ? tw `mt-4` : undefined }
171+ >
172+ < p css = { tw `text-sm text-neutral-400 mb-4` } >
173+ { permissions [ key ] . description }
174+ </ p >
175+ { Object . keys ( permissions [ key ] . keys ) . map ( pkey => (
176+ < PermissionLabel
177+ key = { `permission_${ key } _${ pkey } ` }
178+ htmlFor = { `permission_${ key } _${ pkey } ` }
179+ className = { ( ! canEditUser || editablePermissions . indexOf ( `${ key } .${ pkey } ` ) < 0 ) ? 'disabled' : undefined }
180+ >
181+ < div css = { tw `p-2` } >
182+ < Checkbox
183+ id = { `permission_${ key } _${ pkey } ` }
184+ name = { 'permissions' }
185+ value = { `${ key } .${ pkey } ` }
186+ css = { tw `w-5 h-5 mr-2` }
187+ disabled = { ! canEditUser || editablePermissions . indexOf ( `${ key } .${ pkey } ` ) < 0 }
188+ />
189+ </ div >
190+ < div css = { tw `flex-1` } >
191+ < Label as = { 'p' } css = { tw `font-medium` } > { pkey } </ Label >
192+ { permissions [ key ] . keys [ pkey ] . length > 0 &&
193+ < p css = { tw `text-xs text-neutral-400 mt-1` } >
194+ { permissions [ key ] . keys [ pkey ] }
195+ </ p >
132196 }
133- } }
134- />
135- }
136- </ div >
137- }
138- css = { index > 0 ? tw `mt-4` : undefined }
139- >
140- < p css = { tw `text-sm text-neutral-400 mb-4` } >
141- { permissions [ key ] . description }
142- </ p >
143- { Object . keys ( permissions [ key ] . keys ) . map ( ( pkey , index ) => (
144- < PermissionLabel
145- key = { `permission_${ key } _${ pkey } ` }
146- htmlFor = { `permission_${ key } _${ pkey } ` }
147- css = { [
148- tw `transition-colors duration-75` ,
149- index > 0 ? tw `mt-4 sm:mt-2` : undefined ,
150- ] }
151- className = { ( ! canEditUser || editablePermissions . indexOf ( `${ key } .${ pkey } ` ) < 0 ) ? 'disabled' : undefined }
152- >
153- < div css = { tw `p-2` } >
154- < Checkbox
155- id = { `permission_${ key } _${ pkey } ` }
156- name = { 'permissions' }
157- value = { `${ key } .${ pkey } ` }
158- css = { tw `w-5 h-5 mr-2` }
159- disabled = { ! canEditUser || editablePermissions . indexOf ( `${ key } .${ pkey } ` ) < 0 }
160- />
161- </ div >
162- < div css = { tw `flex-1` } >
163- < Label css = { tw `font-medium` } > { pkey } </ Label >
164- { permissions [ key ] . keys [ pkey ] . length > 0 &&
165- < p css = { tw `text-xs text-neutral-400 mt-1` } >
166- { permissions [ key ] . keys [ pkey ] }
167- </ p >
168- }
169- </ div >
170- </ PermissionLabel >
171- ) ) }
172- </ TitledGreyBox >
173- ) ) }
197+ </ div >
198+ </ PermissionLabel >
199+ ) ) }
200+ </ PermissionTitledBox >
201+ ) ;
202+ } ) }
174203 </ div >
175204 < Can action = { subuser ? 'user.update' : 'user.create' } >
176205 < div css = { tw `pb-6 flex justify-end` } >
@@ -187,8 +216,7 @@ export default ({ subuser, ...props }: Props) => {
187216 const ref = useRef < HTMLHeadingElement > ( null ) ;
188217 const uuid = ServerContext . useStoreState ( state => state . server . data ! . uuid ) ;
189218 const appendSubuser = ServerContext . useStoreActions ( actions => actions . subusers . appendSubuser ) ;
190-
191- const { addError, clearFlashes } = useStoreActions ( ( actions : Actions < ApplicationStore > ) => actions . flashes ) ;
219+ const { clearFlashes, clearAndAddHttpError } = useStoreActions ( ( actions : Actions < ApplicationStore > ) => actions . flashes ) ;
192220
193221 const submit = ( values : Values , { setSubmitting } : FormikHelpers < Values > ) => {
194222 clearFlashes ( 'user:edit' ) ;
@@ -200,7 +228,7 @@ export default ({ subuser, ...props }: Props) => {
200228 . catch ( error => {
201229 console . error ( error ) ;
202230 setSubmitting ( false ) ;
203- addError ( { key : 'user:edit' , message : httpErrorToHuman ( error ) } ) ;
231+ clearAndAddHttpError ( { key : 'user:edit' , error } ) ;
204232
205233 if ( ref . current ) {
206234 ref . current . scrollIntoView ( ) ;
@@ -209,7 +237,9 @@ export default ({ subuser, ...props }: Props) => {
209237 } ;
210238
211239 useEffect ( ( ) => {
212- clearFlashes ( 'user:edit' ) ;
240+ return ( ) => {
241+ clearFlashes ( 'user:edit' ) ;
242+ } ;
213243 } , [ ] ) ;
214244
215245 return (
0 commit comments