1- import React , { forwardRef , memo , useCallback , useEffect , useRef } from 'react' ;
1+ import React , { useContext , useEffect , useRef } from 'react' ;
22import { Subuser } from '@/state/server/subusers' ;
3- import { Form , Formik , FormikHelpers , useFormikContext } from 'formik' ;
3+ import { Form , Formik } from 'formik' ;
44import { array , object , string } from 'yup' ;
5- import Modal , { RequiredModalProps } from '@/components/elements/Modal' ;
65import Field from '@/components/elements/Field' ;
76import { Actions , useStoreActions , useStoreState } from 'easy-peasy' ;
87import { ApplicationStore } from '@/state' ;
9- import TitledGreyBox from '@/components/elements/TitledGreyBox' ;
10- import Checkbox from '@/components/elements/Checkbox' ;
11- import styled from 'styled-components/macro' ;
128import createOrUpdateSubuser from '@/api/server/users/createOrUpdateSubuser' ;
139import { ServerContext } from '@/state/server' ;
1410import FlashMessageRender from '@/components/FlashMessageRender' ;
@@ -17,104 +13,33 @@ import { usePermissions } from '@/plugins/usePermissions';
1713import { useDeepCompareMemo } from '@/plugins/useDeepCompareMemo' ;
1814import tw from 'twin.macro' ;
1915import Button from '@/components/elements/Button' ;
20- import Label from '@/components/elements/Label' ;
21- import Input from '@/components/elements/Input' ;
22- import isEqual from 'react-fast-compare' ;
16+ import PermissionTitleBox from '@/components/server/users/PermissionTitleBox' ;
17+ import asModal from '@/hoc/asModal' ;
18+ import PermissionRow from '@/components/server/users/PermissionRow' ;
19+ import ModalContext from '@/context/ModalContext' ;
2320
2421type Props = {
2522 subuser ?: Subuser ;
26- } & RequiredModalProps ;
23+ } ;
2724
2825interface Values {
2926 email : string ;
3027 permissions : string [ ] ;
3128}
3229
33- const PermissionLabel = styled . label `
34- ${ tw `flex items-center border border-transparent rounded md:p-2 transition-colors duration-75` } ;
35- text-transform: none;
36-
37- &:not(.disabled) {
38- ${ tw `cursor-pointer` } ;
39-
40- &:hover {
41- ${ tw `border-neutral-500 bg-neutral-800` } ;
42- }
43- }
44-
45- &:not(:first-of-type) {
46- ${ tw `mt-4 sm:mt-2` } ;
47- }
48-
49- &.disabled {
50- ${ tw `opacity-50` } ;
51-
52- & input[type="checkbox"]:not(:checked) {
53- ${ tw `border-0` } ;
54- }
55- }
56- ` ;
57-
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 ) ;
30+ const EditSubuserModal = ( { subuser } : Props ) => {
31+ const ref = useRef < HTMLHeadingElement > ( null ) ;
32+ const uuid = ServerContext . useStoreState ( state => state . server . data ! . uuid ) ;
33+ const appendSubuser = ServerContext . useStoreActions ( actions => actions . subusers . appendSubuser ) ;
34+ const { clearFlashes, clearAndAddHttpError } = useStoreActions ( ( actions : Actions < ApplicationStore > ) => actions . flashes ) ;
35+ const { dismiss, toggleSpinner } = useContext ( ModalContext ) ;
10736
108- const EditSubuserModal = forwardRef < HTMLHeadingElement , Props > ( ( { subuser, ...props } , ref ) => {
109- const { isSubmitting } = useFormikContext < Values > ( ) ;
110- const [ canEditUser ] = usePermissions ( subuser ? [ 'user.update' ] : [ 'user.create' ] ) ;
37+ const isRootAdmin = useStoreState ( state => state . user . data ! . rootAdmin ) ;
11138 const permissions = useStoreState ( state => state . permissions . data ) ;
112-
113- const user = useStoreState ( state => state . user . data ! ) ;
114-
11539 // The currently logged in user's permissions. We're going to filter out any permissions
11640 // that they should not need.
11741 const loggedInPermissions = ServerContext . useStoreState ( state => state . server . permissions ) ;
42+ const [ canEditUser ] = usePermissions ( subuser ? [ 'user.update' ] : [ 'user.create' ] ) ;
11843
11944 // The permissions that can be modified by this user.
12045 const editablePermissions = useDeepCompareMemo ( ( ) => {
@@ -123,111 +48,25 @@ const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...pr
12348
12449 const list : string [ ] = ( [ ] as string [ ] ) . concat . apply ( [ ] , Object . values ( cleaned ) ) ;
12550
126- if ( user . rootAdmin || ( loggedInPermissions . length === 1 && loggedInPermissions [ 0 ] === '*' ) ) {
51+ if ( isRootAdmin || ( loggedInPermissions . length === 1 && loggedInPermissions [ 0 ] === '*' ) ) {
12752 return list ;
12853 }
12954
13055 return list . filter ( key => loggedInPermissions . indexOf ( key ) >= 0 ) ;
131- } , [ permissions , loggedInPermissions ] ) ;
132-
133- return (
134- < Modal { ...props } top = { false } showSpinnerOverlay = { isSubmitting } >
135- < h2 css = { tw `text-2xl` } ref = { ref } >
136- { subuser ?
137- `${ canEditUser ? 'Modify' : 'View' } permissions for ${ subuser . email } `
138- :
139- 'Create new subuser'
140- }
141- </ h2 >
142- < FlashMessageRender byKey = { 'user:edit' } css = { tw `mt-4` } />
143- { ( ! user . rootAdmin && loggedInPermissions [ 0 ] !== '*' ) &&
144- < div css = { tw `mt-4 pl-4 py-2 border-l-4 border-cyan-400` } >
145- < p css = { tw `text-sm text-neutral-300` } >
146- Only permissions which your account is currently assigned may be selected when creating or
147- modifying other users.
148- </ p >
149- </ div >
150- }
151- { ! subuser &&
152- < div css = { tw `mt-6` } >
153- < Field
154- name = { 'email' }
155- label = { 'User Email' }
156- description = { 'Enter the email address of the user you wish to invite as a subuser for this server.' }
157- />
158- </ div >
159- }
160- < div css = { tw `my-6` } >
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 >
196- }
197- </ div >
198- </ PermissionLabel >
199- ) ) }
200- </ PermissionTitledBox >
201- ) ;
202- } ) }
203- </ div >
204- < Can action = { subuser ? 'user.update' : 'user.create' } >
205- < div css = { tw `pb-6 flex justify-end` } >
206- < Button type = { 'submit' } css = { tw `w-full sm:w-auto` } >
207- { subuser ? 'Save' : 'Invite User' }
208- </ Button >
209- </ div >
210- </ Can >
211- </ Modal >
212- ) ;
213- } ) ;
214-
215- export default ( { subuser, ...props } : Props ) => {
216- const ref = useRef < HTMLHeadingElement > ( null ) ;
217- const uuid = ServerContext . useStoreState ( state => state . server . data ! . uuid ) ;
218- const appendSubuser = ServerContext . useStoreActions ( actions => actions . subusers . appendSubuser ) ;
219- const { clearFlashes, clearAndAddHttpError } = useStoreActions ( ( actions : Actions < ApplicationStore > ) => actions . flashes ) ;
56+ } , [ isRootAdmin , permissions , loggedInPermissions ] ) ;
22057
221- const submit = ( values : Values , { setSubmitting } : FormikHelpers < Values > ) => {
58+ const submit = ( values : Values ) => {
59+ toggleSpinner ( true ) ;
22260 clearFlashes ( 'user:edit' ) ;
61+
22362 createOrUpdateSubuser ( uuid , values , subuser )
22463 . then ( subuser => {
22564 appendSubuser ( subuser ) ;
226- props . onDismissed ( ) ;
65+ dismiss ( ) ;
22766 } )
22867 . catch ( error => {
22968 console . error ( error ) ;
230- setSubmitting ( false ) ;
69+ toggleSpinner ( false ) ;
23170 clearAndAddHttpError ( { key : 'user:edit' , error } ) ;
23271
23372 if ( ref . current ) {
@@ -236,10 +75,8 @@ export default ({ subuser, ...props }: Props) => {
23675 } ) ;
23776 } ;
23877
239- useEffect ( ( ) => {
240- return ( ) => {
241- clearFlashes ( 'user:edit' ) ;
242- } ;
78+ useEffect ( ( ) => ( ) => {
79+ clearFlashes ( 'user:edit' ) ;
24380 } , [ ] ) ;
24481
24582 return (
@@ -258,8 +95,61 @@ export default ({ subuser, ...props }: Props) => {
25895 } ) }
25996 >
26097 < Form >
261- < EditSubuserModal ref = { ref } subuser = { subuser } { ...props } />
98+ < h2 css = { tw `text-2xl` } ref = { ref } >
99+ { subuser ? `${ canEditUser ? 'Modify' : 'View' } permissions for ${ subuser . email } ` : 'Create new subuser' }
100+ </ h2 >
101+ < FlashMessageRender byKey = { 'user:edit' } css = { tw `mt-4` } />
102+ { ( ! isRootAdmin && loggedInPermissions [ 0 ] !== '*' ) &&
103+ < div css = { tw `mt-4 pl-4 py-2 border-l-4 border-cyan-400` } >
104+ < p css = { tw `text-sm text-neutral-300` } >
105+ Only permissions which your account is currently assigned may be selected when creating or
106+ modifying other users.
107+ </ p >
108+ </ div >
109+ }
110+ { ! subuser &&
111+ < div css = { tw `mt-6` } >
112+ < Field
113+ name = { 'email' }
114+ label = { 'User Email' }
115+ description = { 'Enter the email address of the user you wish to invite as a subuser for this server.' }
116+ />
117+ </ div >
118+ }
119+ < div css = { tw `my-6` } >
120+ { Object . keys ( permissions ) . filter ( key => key !== 'websocket' ) . map ( ( key , index ) => (
121+ < PermissionTitleBox
122+ key = { `permission_${ key } ` }
123+ title = { key }
124+ isEditable = { canEditUser }
125+ permissions = { Object . keys ( permissions [ key ] . keys ) . map ( pkey => `${ key } .${ pkey } ` ) }
126+ css = { index > 0 ? tw `mt-4` : undefined }
127+ >
128+ < p css = { tw `text-sm text-neutral-400 mb-4` } >
129+ { permissions [ key ] . description }
130+ </ p >
131+ { Object . keys ( permissions [ key ] . keys ) . map ( pkey => (
132+ < PermissionRow
133+ key = { `permission_${ key } .${ pkey } ` }
134+ permission = { `${ key } .${ pkey } ` }
135+ disabled = { ! canEditUser || editablePermissions . indexOf ( `${ key } .${ pkey } ` ) < 0 }
136+ />
137+ ) ) }
138+ </ PermissionTitleBox >
139+ ) ) }
140+ </ div >
141+ < Can action = { subuser ? 'user.update' : 'user.create' } >
142+ < div css = { tw `pb-6 flex justify-end` } >
143+ < Button type = { 'submit' } css = { tw `w-full sm:w-auto` } >
144+ { subuser ? 'Save' : 'Invite User' }
145+ </ Button >
146+ </ div >
147+ </ Can >
262148 </ Form >
263149 </ Formik >
264150 ) ;
265151} ;
152+
153+ export default asModal < Props > ( {
154+ top : false ,
155+ } ) ( EditSubuserModal ) ;
0 commit comments