-
Notifications
You must be signed in to change notification settings - Fork 21
Initial handle change ipmlementation to use new member-api endpoint #1432
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| .container { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 20px; | ||
| position: relative; | ||
| } | ||
|
|
||
| .blockForm { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 20px; | ||
| position: relative; | ||
| } | ||
|
|
||
| .actionButtons { | ||
| display: flex; | ||
| justify-content: flex-end; | ||
| gap: 6px; | ||
| } | ||
|
|
||
| .dialogLoadingSpinnerContainer { | ||
| position: absolute; | ||
| width: 64px; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| bottom: 0; | ||
| height: 64px; | ||
| left: 0; | ||
|
|
||
| .spinner { | ||
| background: none; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| /** | ||
| * Dialog edit user handle. | ||
| */ | ||
| import { FC, useCallback, useState } from 'react' | ||
| import { useForm, UseFormReturn } from 'react-hook-form' | ||
| import { toast } from 'react-toastify' | ||
| import _ from 'lodash' | ||
| import classNames from 'classnames' | ||
|
|
||
| import { | ||
| BaseModal, | ||
| Button, | ||
| ConfirmModal, | ||
| InputText, | ||
| LoadingSpinner, | ||
| } from '~/libs/ui' | ||
| import { yupResolver } from '@hookform/resolvers/yup' | ||
|
|
||
| import { FormEditUserHandle, UserInfo } from '../../models' | ||
| import { formEditUserHandleSchema, handleError } from '../../utils' | ||
| import { changeUserHandle } from '../../services' | ||
|
|
||
| import styles from './DialogEditUserHandle.module.scss' | ||
|
|
||
| interface Props { | ||
| className?: string | ||
| open: boolean | ||
| setOpen: (isOpen: boolean) => void | ||
| userInfo: UserInfo | ||
| } | ||
|
|
||
| export const DialogEditUserHandle: FC<Props> = (props: Props) => { | ||
| const [isLoading, setIsLoading] = useState(false) | ||
| const handleClose = useCallback(() => { | ||
| if (!isLoading) { | ||
| props.setOpen(false) | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [isLoading]) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| const [showConfirm, setShowConfirm] = useState(false) | ||
| const { | ||
| register, | ||
| handleSubmit, | ||
| getValues, | ||
| formState: { errors, isValid, isDirty }, | ||
| }: UseFormReturn<FormEditUserHandle> = useForm({ | ||
| defaultValues: { | ||
| newHandle: '', | ||
| }, | ||
| mode: 'all', | ||
| resolver: yupResolver(formEditUserHandleSchema), | ||
| }) | ||
| const onSubmit = useCallback(() => { | ||
| setShowConfirm(true) | ||
| }, []) | ||
| const currentHandle = props.userInfo.handle | ||
| const newHandle = getValues().newHandle ?? '' | ||
| const confirmMessage = `Are you sure you want to change the handle from "${currentHandle}" ` | ||
| + `to "${newHandle}"?` | ||
|
|
||
| return ( | ||
| <> | ||
| <BaseModal | ||
| allowBodyScroll | ||
| blockScroll | ||
| title={`Change handle for ${props.userInfo.handle}`} | ||
| onClose={handleClose} | ||
| open={props.open} | ||
| > | ||
| <form | ||
| className={classNames(styles.container, props.className)} | ||
| onSubmit={handleSubmit(onSubmit)} | ||
| > | ||
| <div className={styles.blockForm}> | ||
| <InputText | ||
| type='text' | ||
| name='currentHandle' | ||
| label='Current Handle' | ||
| placeholder='Enter' | ||
| tabIndex={0} | ||
| forceUpdateValue | ||
| onChange={_.noop} | ||
| disabled | ||
| value={props.userInfo.handle} | ||
| /> | ||
| <InputText | ||
| type='text' | ||
| name='newHandle' | ||
| label='New Handle' | ||
| placeholder='Enter' | ||
| tabIndex={0} | ||
| forceUpdateValue | ||
| onChange={_.noop} | ||
| error={_.get(errors, 'newHandle.message')} | ||
| inputControl={register('newHandle')} | ||
| dirty | ||
| disabled={isLoading} | ||
| /> | ||
| </div> | ||
| <div className={styles.actionButtons}> | ||
| <Button | ||
| secondary | ||
| size='lg' | ||
| onClick={handleClose} | ||
| disabled={isLoading} | ||
| > | ||
| Cancel | ||
| </Button> | ||
| <Button | ||
| type='submit' | ||
| primary | ||
| size='lg' | ||
| disabled={isLoading || !isValid || !isDirty} | ||
| > | ||
| Continue | ||
| </Button> | ||
| </div> | ||
|
|
||
| {isLoading && ( | ||
| <div className={styles.dialogLoadingSpinnerContainer}> | ||
| <LoadingSpinner className={styles.spinner} /> | ||
| </div> | ||
| )} | ||
| </form> | ||
| </BaseModal> | ||
| <ConfirmModal | ||
| allowBodyScroll | ||
| blockScroll | ||
| focusTrapped={false} | ||
| title='Confirm Handle Change' | ||
| action='Confirm' | ||
| onClose={function onClose() { | ||
| setShowConfirm(false) | ||
| }} | ||
| onConfirm={function onConfirm() { | ||
| setShowConfirm(false) | ||
| setIsLoading(true) | ||
| const data = getValues() | ||
| const nextHandle = (data.newHandle ?? '').trim() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
|
|
||
| changeUserHandle(props.userInfo.handle, nextHandle) | ||
| .then(result => { | ||
| setIsLoading(false) | ||
| toast.success('Handle updated successfully') | ||
| props.userInfo.handle = result?.handle ?? nextHandle | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| handleClose() | ||
| }) | ||
| .catch(e => { | ||
| handleError(e) | ||
| setIsLoading(false) | ||
| }) | ||
| }} | ||
| open={showConfirm} | ||
| > | ||
| <div> | ||
| <p>{confirmMessage}</p> | ||
| </div> | ||
| </ConfirmModal> | ||
| </> | ||
| ) | ||
| } | ||
|
|
||
| export default DialogEditUserHandle | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { default as DialogEditUserHandle } from './DialogEditUserHandle' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ import { | |
|
|
||
| import { CopyButton } from '../CopyButton' | ||
| import { DialogEditUserEmail } from '../DialogEditUserEmail' | ||
| import { DialogEditUserHandle } from '../DialogEditUserHandle' | ||
| import { DialogEditUserRoles } from '../DialogEditUserRoles' | ||
| import { DialogEditUserGroups } from '../DialogEditUserGroups' | ||
| import { DialogEditUserSSOLogin } from '../DialogEditUserSSOLogin' | ||
|
|
@@ -82,6 +83,9 @@ export const UsersTable: FC<Props> = props => { | |
| const [showDialogEditUserEmail, setShowDialogEditUserEmail] = useState< | ||
| UserInfo | undefined | ||
| >() | ||
| const [showDialogEditUserHandle, setShowDialogEditUserHandle] = useState< | ||
| UserInfo | undefined | ||
| >() | ||
| const [showDialogEditUserRoles, setShowDialogEditUserRoles] = useState< | ||
| UserInfo | undefined | ||
| >() | ||
|
|
@@ -309,6 +313,8 @@ export const UsersTable: FC<Props> = props => { | |
| function onSelectOption(item: string): void { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| if (item === 'Primary Email') { | ||
| setShowDialogEditUserEmail(data) | ||
| } else if (item === 'Change Handle') { | ||
| setShowDialogEditUserHandle(data) | ||
| } else if (item === 'Roles') { | ||
| setShowDialogEditUserRoles(data) | ||
| } else if (item === 'Groups') { | ||
|
|
@@ -344,6 +350,7 @@ export const UsersTable: FC<Props> = props => { | |
| <DropdownMenuButton | ||
| options={[ | ||
| 'Primary Email', | ||
| 'Change Handle', | ||
| 'Roles', | ||
| 'Groups', | ||
| 'Terms', | ||
|
|
@@ -365,6 +372,7 @@ export const UsersTable: FC<Props> = props => { | |
| <DropdownMenuButton | ||
| options={[ | ||
| 'Primary Email', | ||
| 'Change Handle', | ||
| 'Roles', | ||
| 'Groups', | ||
| 'Terms', | ||
|
|
@@ -449,6 +457,15 @@ export const UsersTable: FC<Props> = props => { | |
| userInfo={showDialogEditUserEmail} | ||
| /> | ||
| )} | ||
| {showDialogEditUserHandle && ( | ||
| <DialogEditUserHandle | ||
| open | ||
| setOpen={function setOpen() { | ||
| setShowDialogEditUserHandle(undefined) | ||
| }} | ||
| userInfo={showDialogEditUserHandle} | ||
| /> | ||
| )} | ||
| {showDialogEditUserRoles && ( | ||
| <DialogEditUserRoles | ||
| open | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| /** | ||
| * Model for edit user handle form | ||
| */ | ||
| export interface FormEditUserHandle { | ||
| newHandle: string | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -222,6 +222,26 @@ export const updateUserEmail = async ( | |
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Update member handle. | ||
| * @param handle current handle. | ||
| * @param newHandle new handle. | ||
| * @returns resolves to member info | ||
| */ | ||
| export const changeUserHandle = async ( | ||
| handle: string, | ||
| newHandle: string, | ||
| ): Promise<MemberInfo> => { | ||
| const payload = { | ||
| newHandle: newHandle.trim(), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| } | ||
|
|
||
| return xhrPatchAsync<typeof payload, MemberInfo>( | ||
| `${EnvironmentConfig.API.V6}/members/${encodeURIComponent(handle)}/change_handle`, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [❗❗ |
||
| payload, | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Update user status. | ||
| * @param userId user id. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ import { | |
| FormEditClient, | ||
| FormEditUserEmail, | ||
| FormEditUserGroup, | ||
| FormEditUserHandle, | ||
| FormEditUserRole, | ||
| FormGroupMembersFilters, | ||
| FormNewBillingAccountResource, | ||
|
|
@@ -442,6 +443,16 @@ export const formEditUserEmailSchema: Yup.ObjectSchema<FormEditUserEmail> | |
| .required('Email address is required.'), | ||
| }) | ||
|
|
||
| /** | ||
| * validation schema for form edit user handle | ||
| */ | ||
| export const formEditUserHandleSchema: Yup.ObjectSchema<FormEditUserHandle> | ||
| = Yup.object({ | ||
| newHandle: Yup.string() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| .trim() | ||
| .required('Handle is required.'), | ||
| }) | ||
|
|
||
| /** | ||
| * validation schema for form edit sso user login | ||
| */ | ||
|
|
||
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.
[⚠️
design]Using
position: absolutefor.dialogLoadingSpinnerContainermay cause layout issues if the parent container's dimensions or position change. Consider using a more flexible layout strategy if the spinner's position is not fixed relative to the viewport or other elements.