-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: style improvements for the User Management page #1131
base: main
Are you sure you want to change the base?
Conversation
|
✅ Deploy Preview for harness-design ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
✅ Deploy Preview for harness-xd-review ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
- Rename dialog hooks to use kebab-case filenames - Remove nested index files for dialog hooks - Simplify export structure for dialog hooks fix: standardize dialog content width and error text classes - Remove `w-full` class from Dialog.Content components - Reorder error text classes for consistency across user management dialogs fix: adjust user dialog UI details - Modify info icon positioning in create user dialog - Reorder error text classes for consistency - Use useEffect to reset form values in edit user dialog refactor: remove form components from user management dialogs - Inline dialog form logic directly into dialog components - Remove separate form components for create, edit, delete, reset password, and remove admin dialogs - Simplify dialog structure by consolidating form and dialog logic - Update locales to support new dialog translations fixes dialogs width decapitalize fix cancels buttons refactor: standardize import paths in user management components - Replace relative imports with absolute paths using @/ prefix - Maintain relative imports in index.ts files - Update import paths in dialog components and providers added locales fixes added empty state added error state added skeleton and nodata refactor dialogs added state provider caption props added no search results state paddings refactor remove unnecesary props drilling remove classes sorting refactor remove unnecessary props drilling fix-margins first versions of modals replace text with span change eslint rule due to conflict between prettier and eslint, prettier already sort classnames Adds promisifyMutation helper to simplify mutation handling Updates user management handlers to use the helper Adds proper typing for handlers using IDataHandlers interface Removes duplicate type definitions added search query make pagination using usePaginationQueryStateWithStore fixes code-review: remove onErrors callbacks fix enter fix pathes refactoring dialogs refactoring types commit: refactor: use store provider for user management hooks This commit refactors the user management page to use a centralized store provider by: Creating UserManagementStoreProvider to manage shared hooks Removing hook props drilling through components Accessing hooks via useUserManagementStore hook in child components Splitting UserManagementPage into container and content components This change improves code organization and reduces prop drilling while maintaining existing functionality. commit: refactor: move dialog context to providers directory This commit reorganizes the dialog-related code by: Moving DialogsProvider from context/ to providers/ directory Updating import paths across components to use the new location 3. Removing unused context files and types Consolidating dialog-related code for better maintainability This is a structural change that improves code organization while maintaining existing functionality. remove redundant index from dialog commit: refactor: migrate dialog state management to UI layer The commit refactors the user management dialog state handling by: Moving dialog state management from container to UI layer using DialogsProvider Converting mutation handlers to return Promises for proper async handling Removing redundant dialog state management code from container Consolidating error and loading states into dedicated objects Moving EActiveTab enum to UI package for better organization This change improves separation of concerns by keeping UI state management in the UI layer while maintaining the same functionality. refactor: reorganize validation schemas and types - Move Zod schemas to dedicated schema files - Update type definitions to use schema inference - Fix import paths to use absolute imports - Remove duplicate schema definitions from components - Centralize types in dedicated type files refactor(user-management): reorganize dialogs structure - Move all dialogs to dedicated folders with proper structure - Add types and index files for each dialog - Update imports and exports - Add new translations for empty state - Improve code organization in UserManagementPage first version of filters and sorting remove unnecessary fragment added tabs fixes after design review
d1c7a50
to
66d258f
Compare
const handleCreateUser = (data: { uid: string; email: string; display_name: string }) => { | ||
createUser({ | ||
const handleCreateUser: IDataHandlers['handleCreateUser'] = data => { | ||
return promisifyMutation(createUser, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of wrapping things in promisifyMutation
, you can just the mutateAsync
instead of mutate
to get the promise-based version from Tanstack Query
https://tanstack.com/query/latest/docs/framework/react/guides/mutations#promises
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can kill this entirely and just use mutateAsync
instead of mutate
"addNewUser": "Add a new user", | ||
"userId": "User ID", | ||
"userIdHint": "User ID cannot be changed once created", | ||
"enterUsername": "Enter user name", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd argue that it's username
, not user name
. Or is this the user's name? In that case, we probably should just say "Enter name" to remove any ambiguity.
"enterUsername": "Enter user name", | |
"enterUsername": "Enter username", |
"enterUsername": "Enter user name", | |
"enterName": "Enter name", |
<Button variant="outline" type="button" onClick={onClose} disabled={isCreatingUser}> | ||
{t('views:userManagement.cancel', 'Cancel')} | ||
</Button> | ||
<Button type="button" onClick={handleSubmit(onSubmit)} disabled={isCreatingUser}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if the Dialog
component allows this, but is it possible wrap the content in a form tag so that submission via keyboard would be possible? Instead of the submission being a click handler, it would then be set on the form and this button could then just be set as type="submit"
.
<Dialog.Content>
<form onSubmit={handleSubmit(onSubmit)}>
<Dialog.Header>...</Dialog.Header>
{/* form stuff */}
<Dialog.Footer>
<ButtonGroup>
<Button onclick={onClose}>Cancel</Button>
<Button type="submit">Invite user</Button>
</ButtonGroup>
</Dialog.Footer>
</form>
</Dialog.Content>
Or something like that. Ideally most/all dialogs should be confirmable using enter and dismissible using escape without first having to focus those buttons.
uid: z.string().min(1, { message: 'Please provide a user ID' }), | ||
email: z.string().email({ message: 'Please enter a valid email address' }), | ||
display_name: z.string().min(1, { message: 'Please provide a display name' }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will these display? Or will they somehow be replaced by the translations?
export interface ICreateUserDialogProps { | ||
handleCreateUser: (data: NewUserFields) => void | ||
open: boolean | ||
onClose: () => void | ||
} | ||
|
||
export interface ICreateUserFormProps { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm really not a fan of the I
prefix on interfaces. For me, the Props
suffix is indication enough that it is a type.
export interface ICreateUserDialogProps { | |
handleCreateUser: (data: NewUserFields) => void | |
open: boolean | |
onClose: () => void | |
} | |
export interface ICreateUserFormProps { | |
export interface CreateUserDialogProps { | |
handleCreateUser: (data: NewUserFields) => void | |
open: boolean | |
onClose: () => void | |
} | |
export interface CreateUserFormProps { |
I also prefer when the component props interfaces live with their respective components. Easier to keep them in sync and see at a glance.
Neither are blockers since this is in an internal component of a view.
const onSubmit: SubmitHandler<EditUserFormType> = data => { | ||
handleUpdateUser(data) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this intermediate function? Could we just use handleUpdateUser
directly? i.e.
<Button type="button" onClick={handleSubmit(handleUpdateUser)} disabled={isUpdatingUser}>
</Dialog.Title> | ||
</Dialog.Header> | ||
|
||
{/* TODO: investigate how to make user login bold with font-weight: 600 */} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not quite sure what "user login" is referring to 🤔
</span> | ||
|
||
{generatePassword && ( | ||
<Input |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to confirm, the user who will be generating a password is unlikely to be the user who the password is for?
export const useDialogData = () => { | ||
const { useAdminListUsersStore } = useUserManagementStore() | ||
|
||
const { setUser, setPassword, setGeteneratePassword } = useAdminListUsersStore() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like there's a typo in the store?
const { setUser, setPassword, setGeteneratePassword } = useAdminListUsersStore() | |
const { setUser, setPassword, setGeneratePassword } = useAdminListUsersStore() |
const prepareDialogData = (user: UsersProps | null, dialogType: DialogLabels) => { | ||
if (user) setUser(user) | ||
|
||
if (dialogType === DialogLabels.RESET_PASSWORD) { | ||
setGeteneratePassword(false) | ||
setPassword(generateAlphaNumericHash(10)) | ||
|
||
return | ||
} | ||
|
||
if (dialogType === DialogLabels.CREATE_USER) { | ||
setGeteneratePassword(true) | ||
setPassword(generateAlphaNumericHash(10)) | ||
|
||
return | ||
} | ||
} | ||
|
||
const handleDialogOpen = (user: UsersProps | null, dialogType: DialogLabels) => { | ||
prepareDialogData(user, dialogType) | ||
openDialog(dialogType) | ||
} | ||
|
||
return { | ||
handleDialogOpen | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth pointing out that this will not be dependency array safe. Easiest way is to wrap this whole bit in a useMemo
const prepareDialogData = (user: UsersProps | null, dialogType: DialogLabels) => { | |
if (user) setUser(user) | |
if (dialogType === DialogLabels.RESET_PASSWORD) { | |
setGeteneratePassword(false) | |
setPassword(generateAlphaNumericHash(10)) | |
return | |
} | |
if (dialogType === DialogLabels.CREATE_USER) { | |
setGeteneratePassword(true) | |
setPassword(generateAlphaNumericHash(10)) | |
return | |
} | |
} | |
const handleDialogOpen = (user: UsersProps | null, dialogType: DialogLabels) => { | |
prepareDialogData(user, dialogType) | |
openDialog(dialogType) | |
} | |
return { | |
handleDialogOpen | |
} | |
return useMemo(() => { | |
const prepareDialogData = (user: UsersProps | null, dialogType: DialogLabels) => { | |
if (user) setUser(user) | |
if (dialogType === DialogLabels.RESET_PASSWORD) { | |
setGeteneratePassword(false) | |
setPassword(generateAlphaNumericHash(10)) | |
return | |
} | |
if (dialogType === DialogLabels.CREATE_USER) { | |
setGeteneratePassword(true) | |
setPassword(generateAlphaNumericHash(10)) | |
return | |
} | |
} | |
const handleDialogOpen = (user: UsersProps | null, dialogType: DialogLabels) => { | |
prepareDialogData(user, dialogType) | |
openDialog(dialogType) | |
} | |
return { | |
handleDialogOpen | |
} | |
}, [setUser, setPassword, setGeteneratePassword, openDialog]) |
export const useDialogHandlers = (handlers: IDialogHandlers) => { | ||
const { closeDialog } = useDialogs() | ||
|
||
return { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also won't be dependency array safe. Easy fix with a useMemo
around this whole return
|
||
return { | ||
handleCreateUser: async (...args: Parameters<typeof handlers.handleCreateUser>) => { | ||
const result = await handlers.handleCreateUser(...args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if these handlers fail?
const { totalPages, page: currentPage, setPage } = useAdminListUsersStore() | ||
|
||
return ( | ||
<SandboxLayout.Main className="max-w-[1092px]"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1092 is a very peculiarly specific number 🤔
Description:
This pull request adds style improvements as well as refactoring of current components for the
User Management
pageBefore:

After:

Before:

After:

Before:

After:

Before:

After:

Before:

After:

Before:

After:
