Skip to content
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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

andrewgolovanov
Copy link
Collaborator

Description:
This pull request adds style improvements as well as refactoring of current components for the User Management page

Before:
image

After:
image

Before:
image

After:
image

Before:
image

After:
image

Before:
image

After:
image

Before:
image

After:
image

Before:
image

After:
image

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link

netlify bot commented Feb 19, 2025

Deploy Preview for harness-design ready!

Name Link
🔨 Latest commit 66d258f
🔍 Latest deploy log https://app.netlify.com/sites/harness-design/deploys/67b6106d4ff90d0008ff04b1
😎 Deploy Preview https://deploy-preview-1131--harness-design.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link

netlify bot commented Feb 19, 2025

Deploy Preview for harness-xd-review ready!

Name Link
🔨 Latest commit 66d258f
🔍 Latest deploy log https://app.netlify.com/sites/harness-xd-review/deploys/67b6106d8d249f00089fff69
😎 Deploy Preview https://deploy-preview-1131--harness-xd-review.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

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
const handleCreateUser = (data: { uid: string; email: string; display_name: string }) => {
createUser({
const handleCreateUser: IDataHandlers['handleCreateUser'] = data => {
return promisifyMutation(createUser, {
Copy link
Collaborator

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

Copy link
Collaborator

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",
Copy link
Collaborator

@knagurski knagurski Feb 26, 2025

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.

Suggested change
"enterUsername": "Enter user name",
"enterUsername": "Enter username",
Suggested change
"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}>
Copy link
Collaborator

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.

Comment on lines +4 to +6
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' })
Copy link
Collaborator

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?

Comment on lines +6 to +12
export interface ICreateUserDialogProps {
handleCreateUser: (data: NewUserFields) => void
open: boolean
onClose: () => void
}

export interface ICreateUserFormProps {
Copy link
Collaborator

@knagurski knagurski Feb 26, 2025

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.

Suggested change
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.

Comment on lines +44 to +46
const onSubmit: SubmitHandler<EditUserFormType> = data => {
handleUpdateUser(data)
}
Copy link
Collaborator

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 */}
Copy link
Collaborator

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
Copy link
Collaborator

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()
Copy link
Collaborator

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?

Suggested change
const { setUser, setPassword, setGeteneratePassword } = useAdminListUsersStore()
const { setUser, setPassword, setGeneratePassword } = useAdminListUsersStore()

Comment on lines +14 to +39
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
}
Copy link
Collaborator

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

Suggested change
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 {
Copy link
Collaborator

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)
Copy link
Collaborator

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]">
Copy link
Collaborator

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 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants