From 6d72cf1f82fafeca72761ac18c536eed784a07b0 Mon Sep 17 00:00:00 2001 From: Bekbolsun Date: Fri, 16 Feb 2024 17:59:59 +0600 Subject: [PATCH] implement edit username logic in edit modal --- .../Modal/ModalEditName/ModalEditName.tsx | 138 ++++++++++++++++++ .../Modal/ModalLogin/ModalLogin.tsx | 3 +- src/pages/KeyPage/Key.Page.tsx | 25 +++- .../KeyPage/components/UserValueSection.tsx | 7 +- src/shared/Button/Button.tsx | 4 +- src/shared/Input/Input.tsx | 15 +- src/types/modal.ts | 1 + 7 files changed, 178 insertions(+), 15 deletions(-) create mode 100644 src/components/Modal/ModalEditName/ModalEditName.tsx diff --git a/src/components/Modal/ModalEditName/ModalEditName.tsx b/src/components/Modal/ModalEditName/ModalEditName.tsx new file mode 100644 index 0000000..136fea9 --- /dev/null +++ b/src/components/Modal/ModalEditName/ModalEditName.tsx @@ -0,0 +1,138 @@ +import { CheckmarkIcon } from '@/assets' +import { useEnqueueSnackbar } from '@/hooks/useEnqueueSnackbar' +import { useModalSearchParams } from '@/hooks/useModalSearchParams' +import { swicCall } from '@/modules/swic' +import { Button } from '@/shared/Button/Button' +import { Input } from '@/shared/Input/Input' +import { LoadingSpinner } from '@/shared/LoadingSpinner/LoadingSpinner' +import { Modal } from '@/shared/Modal/Modal' +import { selectKeys } from '@/store' +import { useAppSelector } from '@/store/hooks/redux' +import { MODAL_PARAMS_KEYS } from '@/types/modal' +import { DOMAIN } from '@/utils/consts' +import { fetchNip05 } from '@/utils/helpers/helpers' +import { Stack, Typography, useTheme } from '@mui/material' +import { ChangeEvent, Fragment, useCallback, useEffect, useState } from 'react' +import { useSearchParams } from 'react-router-dom' +import { useDebounce } from 'use-debounce' + +export const ModalEditName = () => { + const keys = useAppSelector(selectKeys) + const notify = useEnqueueSnackbar() + + const [searchParams] = useSearchParams() + const name = searchParams.get('name') || '' + const npub = searchParams.get('npub') || '' + + const { palette } = useTheme() + + const { getModalOpened, createHandleCloseReplace } = useModalSearchParams() + const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.EDIT_NAME) + const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.EDIT_NAME) + + const [enteredName, setEnteredName] = useState('') + const [debouncedName] = useDebounce(enteredName, 300) + const isNameEqual = debouncedName === name + + const [isAvailable, setIsAvailable] = useState(true) + const [isChecking, setIsChecking] = useState(false) + + const [isLoading, setIsLoading] = useState(false) + + const checkIsUsernameAvailable = useCallback(async () => { + if (!debouncedName.trim().length) return undefined + try { + setIsChecking(true) + const npubNip05 = await fetchNip05(`${debouncedName}@${DOMAIN}`) + setIsAvailable(!npubNip05) + setIsChecking(false) + } catch (error) { + setIsAvailable(true) + setIsChecking(false) + } + }, [debouncedName]) + + useEffect(() => { + checkIsUsernameAvailable() + }, [checkIsUsernameAvailable]) + + useEffect(() => { + setEnteredName(name) + return () => { + if (isModalOpened) setEnteredName('') + } + // eslint-disable-next-line + }, [isModalOpened]) + + const handleNameChange = (e: ChangeEvent) => setEnteredName(e.target.value) + + const getInputHelperText = () => { + if (!debouncedName.trim().length || isNameEqual) return '' + if (isChecking) return 'Loading...' + if (!isAvailable) return 'Already taken' + return ( + + Available + + ) + } + const inputHelperText = getInputHelperText() + + const getHelperTextColor = useCallback(() => { + if (!debouncedName || isChecking || isNameEqual) return palette.textSecondaryDecorate.main + return isAvailable ? palette.success.main : palette.error.main + // deps + }, [debouncedName, isAvailable, isChecking, isNameEqual, palette]) + + const isNpubExists = npub.trim().length && keys.some((key) => key.npub === npub) + if (isModalOpened && !isNpubExists) { + handleCloseModal() + return null + } + + const isEditButtonDisabled = isNameEqual || isChecking || isLoading || !enteredName.trim().length + + const handleEditName = async () => { + try { + setIsLoading(true) + await swicCall('editName', npub, debouncedName) + notify('Username successfully editted!', 'success') + setEnteredName(debouncedName) + setIsLoading(false) + } catch (error) { + setIsLoading(false) + } + } + + return ( + + + + @{DOMAIN}} + helperText={inputHelperText} + onChange={handleNameChange} + value={enteredName} + helperTextProps={{ + sx: { + '&.helper_text': { + color: getHelperTextColor(), + }, + }, + }} + /> + + + + + + + + + ) +} diff --git a/src/components/Modal/ModalLogin/ModalLogin.tsx b/src/components/Modal/ModalLogin/ModalLogin.tsx index ad3d1ad..ec02647 100644 --- a/src/components/Modal/ModalLogin/ModalLogin.tsx +++ b/src/components/Modal/ModalLogin/ModalLogin.tsx @@ -100,13 +100,14 @@ export const ModalLogin = () => { if (isPopup && isModalOpened) { swicCall('fetchPendingRequests', npub, appNpub) - fetchNpubNames(npub).then(names => { + fetchNpubNames(npub).then((names) => { if (names.length) { setValue('username', `${names[0]}@${DOMAIN}`) } }) } } + // eslint-disable-next-line }, [searchParams, isModalOpened, setValue]) useEffect(() => { diff --git a/src/pages/KeyPage/Key.Page.tsx b/src/pages/KeyPage/Key.Page.tsx index 3f1547f..3244e97 100644 --- a/src/pages/KeyPage/Key.Page.tsx +++ b/src/pages/KeyPage/Key.Page.tsx @@ -1,6 +1,6 @@ import { useAppSelector } from '../../store/hooks/redux' import { Navigate, useParams, useSearchParams } from 'react-router-dom' -import { Stack } from '@mui/material' +import { Box, IconButton, Stack } from '@mui/material' import { StyledIconButton } from './styled' import { SettingsIcon, ShareIcon } from '@/assets' import { Apps } from './components/Apps' @@ -19,6 +19,9 @@ import { useLiveQuery } from 'dexie-react-hooks' import { checkNpubSyncQuerier } from './utils' import { DOMAIN } from '@/utils/consts' import { useCallback } from 'react' +import { InputCopyButton } from '@/shared/InputCopyButton/InputCopyButton' +import MoreHorizRoundedIcon from '@mui/icons-material/MoreHorizRounded' +import { ModalEditName } from '@/components/Modal/ModalEditName/ModalEditName' const KeyPage = () => { const { npub = '' } = useParams<{ npub: string }>() @@ -54,6 +57,13 @@ const KeyPage = () => { const handleOpenConnectAppModal = () => handleOpen(MODAL_PARAMS_KEYS.CONNECT_APP) const handleOpenSettingsModal = () => handleOpen(MODAL_PARAMS_KEYS.SETTINGS) + const handleOpenEditNameModal = () => + handleOpen(MODAL_PARAMS_KEYS.EDIT_NAME, { + search: { + name: key.name || '', + npub, + }, + }) return ( <> @@ -64,13 +74,20 @@ const KeyPage = () => { + + + + + + } explanationType={EXPLANATION_MODAL_KEYS.LOGIN} /> } explanationType={EXPLANATION_MODAL_KEYS.NPUB} /> @@ -88,11 +105,13 @@ const KeyPage = () => { + + ) } diff --git a/src/pages/KeyPage/components/UserValueSection.tsx b/src/pages/KeyPage/components/UserValueSection.tsx index a3520ac..935a665 100644 --- a/src/pages/KeyPage/components/UserValueSection.tsx +++ b/src/pages/KeyPage/components/UserValueSection.tsx @@ -3,7 +3,6 @@ import { Box, Stack } from '@mui/material' import { EXPLANATION_MODAL_KEYS, MODAL_PARAMS_KEYS } from '@/types/modal' import { SectionTitle } from '@/shared/SectionTitle/SectionTitle' import { AppLink } from '@/shared/AppLink/AppLink' -import { InputCopyButton } from '@/shared/InputCopyButton/InputCopyButton' import { StyledInput } from '../styled' import { useModalSearchParams } from '@/hooks/useModalSearchParams' @@ -11,10 +10,10 @@ type UserValueSectionProps = { title: string value: string explanationType: EXPLANATION_MODAL_KEYS - copyValue: string + endAdornment?: React.ReactNode } -const UserValueSection: FC = ({ title, value, explanationType, copyValue }) => { +const UserValueSection: FC = ({ title, value, explanationType, endAdornment }) => { const { handleOpen } = useModalSearchParams() const handleOpenExplanationModal = (type: EXPLANATION_MODAL_KEYS) => { @@ -30,7 +29,7 @@ const UserValueSection: FC = ({ title, value, explanation {title} handleOpenExplanationModal(explanationType)} /> - } /> + ) } diff --git a/src/shared/Button/Button.tsx b/src/shared/Button/Button.tsx index 683ecde..97886d8 100644 --- a/src/shared/Button/Button.tsx +++ b/src/shared/Button/Button.tsx @@ -39,9 +39,9 @@ const StyledButton = styled( background: theme.palette.primary.main, }, color: theme.palette.text.secondary, - '&.disabled': { + '&.button.disabled': { color: theme.palette.text.secondary, - background: `${theme.palette.primary.main}50`, + background: `${theme.palette.primary.main}75`, cursor: 'not-allowed', }, } diff --git a/src/shared/Input/Input.tsx b/src/shared/Input/Input.tsx index 9cb6f74..d65a39a 100644 --- a/src/shared/Input/Input.tsx +++ b/src/shared/Input/Input.tsx @@ -26,7 +26,12 @@ export const Input = forwardRef( {label} ) : null} - + {helperText ? ( {helperText} @@ -41,20 +46,20 @@ const StyledInputContainer = styled((props: BoxProps) => )(({ const isDark = theme.palette.mode === 'dark' return { width: '100%', - '& > .input': { + '& > .input_root': { background: isDark ? '#000000A8' : '#000', color: theme.palette.common.white, padding: '0.75rem 1rem', borderRadius: '1rem', border: '0.3px solid #FFFFFF54', fontSize: '0.875rem', - '& input::placeholder': { - color: '#fff', - }, '&.error': { border: '0.3px solid ' + theme.palette.error.main, }, }, + '& .input:is(.disabled, &)': { + WebkitTextFillColor: '#ffffff80', + }, '& > .helper_text': { margin: '0.5rem 1rem 0', color: theme.palette.text.primary, diff --git a/src/types/modal.ts b/src/types/modal.ts index bf50959..b99c479 100644 --- a/src/types/modal.ts +++ b/src/types/modal.ts @@ -10,6 +10,7 @@ export enum MODAL_PARAMS_KEYS { CONFIRM_EVENT = 'confirm-event', ACTIVITY = 'activity', APP_DETAILS = 'app-details', + EDIT_NAME = 'edit-name', } export enum EXPLANATION_MODAL_KEYS {