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

feature/Users who request already-taken usernames should be invited to request a new username #1843

Merged
merged 44 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
90e3c83
desktop - container, stories, test
Kacper-RF Sep 20, 2023
1c76661
adjust component
Kacper-RF Sep 21, 2023
26032b6
Remove obsolete registrar code
vinkabuki Sep 21, 2023
0ec4d44
Unify registration function for owner and user
vinkabuki Sep 21, 2023
4bbe2cf
Clean up registrar methods logic
vinkabuki Sep 21, 2023
c4ba087
mobile - screens, component, tests
Kacper-RF Sep 21, 2023
e40c4d5
add changelog
Kacper-RF Sep 21, 2023
df94c25
organize code
vinkabuki Sep 21, 2023
3a80426
Remove test
vinkabuki Sep 21, 2023
36d58a1
Update changelog
vinkabuki Sep 21, 2023
2e8aec1
skip randomly failing websockets tests
vinkabuki Sep 21, 2023
0425139
connection with state manager
Kacper-RF Sep 22, 2023
884634a
next steps for pub and priv key
Kacper-RF Sep 22, 2023
762919e
map old messages to new nickname
Kacper-RF Sep 25, 2023
799ba21
use dictionary with counter for usernames
Kacper-RF Sep 26, 2023
bb71d69
changelog
Kacper-RF Sep 26, 2023
7ee91db
Add logic for graceful certificates and csrs handling in registrar
vinkabuki Sep 26, 2023
f3ae4db
Remove inadequate registrar tests
vinkabuki Sep 26, 2023
7b6f862
state manager tests
Kacper-RF Sep 26, 2023
309f6bd
changelog
Kacper-RF Sep 26, 2023
3b96e40
fix in selector, error handling when using taken nickname
Kacper-RF Sep 27, 2023
61a9437
check only registered users
Kacper-RF Sep 27, 2023
ee4fae8
Clean code and tests
vinkabuki Sep 27, 2023
9ce135e
merge
Kacper-RF Sep 27, 2023
c91d0fc
desktop test and linter
Kacper-RF Sep 27, 2023
781a2bb
add test case for registerCertificate saga
Kacper-RF Sep 27, 2023
b1d2f6b
show username taken screen on mobile, error handling on mobile
Kacper-RF Sep 28, 2023
bf46d04
fix mobile saga test
Kacper-RF Sep 28, 2023
ef746a0
Add tests for extracting pending csrs
vinkabuki Sep 28, 2023
3332578
merge
Kacper-RF Sep 28, 2023
c0b42f9
registerUsername saga test for case with username taken
Kacper-RF Sep 28, 2023
1fca2fe
cr fixes, fix updateCertificate saga
Kacper-RF Sep 29, 2023
5375941
Improve code quality after code review
vinkabuki Sep 29, 2023
e019b86
Merge branch 'feature/1697' of github.com:TryQuiet/quiet into feature…
vinkabuki Sep 29, 2023
b3ac445
cr fixes
Kacper-RF Oct 4, 2023
be32eb0
merge develop
Kacper-RF Oct 4, 2023
e6f7c42
Merge branch 'feature/1697' of github.com:TryQuiet/quiet into feature…
Kacper-RF Oct 4, 2023
eb28569
cleanup
Kacper-RF Oct 4, 2023
5617d5e
cleanup
Kacper-RF Oct 4, 2023
d60f9da
fix PR comments
vinkabuki Oct 4, 2023
7240e51
PR comments
vinkabuki Oct 5, 2023
525ea8b
Merged develop to feature/1697
vinkabuki Oct 5, 2023
203ede1
update snapshots
Kacper-RF Oct 5, 2023
9702f94
fix extractPendingCsrs method flaws
vinkabuki Oct 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@
* C4 for Quiet architecture. Context and Container diagrams.

* Invite tab as default in settings

Kacper-RF marked this conversation as resolved.
Show resolved Hide resolved
* UI layer for taken usernames for desktop and mobile
2 changes: 2 additions & 0 deletions packages/desktop/src/renderer/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import ChannelCreationModal from './components/ChannelCreationModal/ChannelCreat
import { SaveStateComponent } from './components/SaveState/SaveStateComponent'
import UnregisteredModalContainer from './components/widgets/userLabel/unregistered/UnregisteredModal.container'
import DuplicateModalContainer from './components/widgets/userLabel/duplicate/DuplicateModal.container'
import UsernameTakenModalContainer from './components/widgets/usernameTakenModal/UsernameTakenModal.container'
// Trigger lerna

export const persistor = persistStore(store)
Expand All @@ -49,6 +50,7 @@ export default () => {
<SearchModal />
<ErrorModal />
<LoadingPanel />
<UsernameTakenModalContainer />
<ChannelCreationModal />
<CreateChannel />
<JoinCommunity />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@ import classNames from 'classnames'
import { Controller, useForm } from 'react-hook-form'
import Typography from '@mui/material/Typography'
import Grid from '@mui/material/Grid'

import WarningIcon from '@mui/icons-material/Warning'

import Modal from '../ui/Modal/Modal'

import { LoadingButton } from '../ui/LoadingButton/LoadingButton'
import { TextInput } from '../../forms/components/textInput'
import { userNameField } from '../../forms/fields/createUserFields'

import { parseName } from '@quiet/common'

const PREFIX = 'CreateUsernameComponent'
const PREFIX = 'CreateUsernameComponent-'

const classes = {
focus: `${PREFIX}focus`,
Expand All @@ -31,6 +28,10 @@ const classes = {
rootBar: `${PREFIX}rootBar`,
progressBar: `${PREFIX}progressBar`,
info: `${PREFIX}info`,
inputLabel: `${PREFIX}inputLabel`,
marginMedium: `${PREFIX}marginMedium`,
buttonModern: `${PREFIX}buttonModern`,
buttonMargin: `${PREFIX}buttonMargin`,
}

const StyledModalContent = styled(Grid)(({ theme }) => ({
Expand Down Expand Up @@ -81,6 +82,11 @@ const StyledModalContent = styled(Grid)(({ theme }) => ({
fontWeight: 'normal',
},

[`& .${classes.buttonModern}`]: {
borderRadius: 8,
width: 110,
},

[`& .${classes.title}`]: {
marginBottom: 24,
},
Expand Down Expand Up @@ -113,6 +119,16 @@ const StyledModalContent = styled(Grid)(({ theme }) => ({
lineHeight: '19px',
color: theme.palette.colors.darkGray,
},

[`& .${classes.inputLabel}`]: {
marginTop: 24,
marginBottom: 2,
color: theme.palette.colors.black30,
},

[`& .${classes.marginMedium}`]: {
marginTop: 24,
},
}))

const userFields = {
Expand All @@ -123,12 +139,19 @@ interface CreateUserValues {
userName: string
}

export enum UsernameVariant {
NEW = 'new',
TAKEN = 'taken',
}

export interface CreateUsernameComponentProps {
open: boolean
registerUsername: (name: string) => void
certificateRegistrationError?: string
certificate?: string | null
handleClose: () => void
currentUsername?: string
variant?: UsernameVariant
}

export const CreateUsernameComponent: React.FC<CreateUsernameComponentProps> = ({
Expand All @@ -137,7 +160,12 @@ export const CreateUsernameComponent: React.FC<CreateUsernameComponentProps> = (
certificateRegistrationError,
certificate,
handleClose,
currentUsername,
variant = UsernameVariant.NEW,
}) => {
const isNewUser = variant === UsernameVariant.NEW
const [isUsernameRequested, setIsUsernameRequested] = useState(false)

const [formSent, setFormSent] = useState(false)
const [userName, setUserName] = useState('')
const [parsedNameDiffers, setParsedNameDiffers] = useState(false)
Expand All @@ -156,7 +184,12 @@ export const CreateUsernameComponent: React.FC<CreateUsernameComponentProps> = (
})

const onSubmit = (values: CreateUserValues) => {
submitForm(registerUsername, values, setFormSent)
if (isNewUser) {
submitForm(registerUsername, values, setFormSent)
} else {
registerUsername(parseName(values.userName))
setIsUsernameRequested(true)
}
}

const submitForm = (
Expand Down Expand Up @@ -188,77 +221,137 @@ export const CreateUsernameComponent: React.FC<CreateUsernameComponentProps> = (
}, [certificateRegistrationError])

return (
<Modal open={open} handleClose={handleClose} testIdPrefix='createUsername' isCloseDisabled={true}>
<Modal
open={open}
handleClose={handleClose}
testIdPrefix='createUsername'
isCloseDisabled={isNewUser}
isBold={!isNewUser}
addBorder={!isNewUser}
title={isNewUser ? undefined : isUsernameRequested ? 'New username requested' : 'Username taken'}
>
<StyledModalContent container direction='column'>
<>
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container justifyContent='flex-start' direction='column' className={classes.fullContainer}>
<Typography variant='h3' className={classes.title}>
Register a username
</Typography>
<Typography variant='body2'>Choose your favorite username</Typography>
<Controller
control={control}
defaultValue={''}
rules={userFields.userName.validation}
name={'userName'}
render={({ field }) => (
<TextInput
{...userFields.userName.fieldProps}
fullWidth
classes={classNames({
[classes.focus]: true,
[classes.margin]: true,
[classes.error]: errors.userName,
})}
placeholder={'Enter a username'}
errors={errors}
onPaste={e => e.preventDefault()}
variant='outlined'
onchange={event => {
event.persist()
const value = event.target.value
onChange(value)
// Call default
field.onChange(event)
}}
onblur={() => {
field.onBlur()
}}
value={field.value}
/>
{!isUsernameRequested ? (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container justifyContent='flex-start' direction='column' className={classes.fullContainer}>
{isNewUser ? (
<>
<Typography variant='h3' className={classes.title}>
Register a username
</Typography>
<Typography variant='body2'>Choose your favorite username</Typography>
</>
) : (
<>
<Typography variant='body2' className={classes.marginMedium}>
We’re sorry, but the username <strong>{currentUsername && `@${currentUsername}`}</strong> was
already claimed by someone else. <br />
Can you choose another name?
</Typography>

<Typography variant='body2' className={classes.inputLabel}>
Enter username
</Typography>
</>
)}
/>
<div className={classes.gutter}>
{!errors.userName && userName.length > 0 && parsedNameDiffers && (
<Grid container alignItems='center' direction='row'>
<Grid item className={classes.iconDiv}>
<WarningIcon className={classes.warrningIcon} />
</Grid>
<Grid item xs>
<Typography
variant='body2'
className={classes.warrningMessage}
data-testid={'createUserNameWarning'}
>
Your username will be registered as <b>{`@${userName}`}</b>
</Typography>
</Grid>
</Grid>

<Controller
control={control}
defaultValue={''}
rules={userFields.userName.validation}
name={'userName'}
render={({ field }) => (
<TextInput
{...userFields.userName.fieldProps}
fullWidth
classes={classNames({
[classes.focus]: true,
[classes.margin]: true,
[classes.error]: errors.userName,
})}
placeholder={isNewUser ? 'Enter a username' : 'Username'}
errors={errors}
onPaste={e => e.preventDefault()}
variant='outlined'
onchange={event => {
event.persist()
const value = event.target.value
onChange(value)
// Call default
field.onChange(event)
}}
onblur={() => {
field.onBlur()
}}
value={field.value}
/>
)}
/>

{!isNewUser && (
<Typography variant='caption' style={{ marginTop: 8 }}>
Your username will be public, but you can choose any name you like. No spaces or special characters.
Kacper-RF marked this conversation as resolved.
Show resolved Hide resolved
Lowercase letters and numbers only.
leblowl marked this conversation as resolved.
Show resolved Hide resolved
</Typography>
)}
</div>
<LoadingButton
variant='contained'
color='primary'
inProgress={waitingForResponse}
disabled={waitingForResponse}
type='submit'
text={'Register'}
classes={{ button: classes.button }}
/>
</Grid>
</form>
</>
<div className={classes.gutter}>
{!errors.userName && userName.length > 0 && parsedNameDiffers && (
<Grid container alignItems='center' direction='row'>
<Grid item className={classes.iconDiv}>
<WarningIcon className={classes.warrningIcon} />
</Grid>
<Grid item xs>
<Typography
variant='body2'
className={classes.warrningMessage}
data-testid={'createUserNameWarning'}
>
Your username will be registered as <b>{`@${userName}`}</b>
</Typography>
</Grid>
</Grid>
)}
</div>
<LoadingButton
variant='contained'
color='primary'
inProgress={waitingForResponse}
disabled={waitingForResponse}
type='submit'
text={isNewUser ? 'Register' : 'Continue'}
classes={{
button: classNames({
[classes.button]: true,
[classes.buttonModern]: !isNewUser,
}),
}}
/>
</Grid>
</form>
</>
) : (
<>
<Typography variant='body2' className={classes.marginMedium}>
Great! Your new username should be registered automatically the next time the community owner is online.
</Typography>

<LoadingButton
variant='contained'
color='primary'
type='submit'
text={'Continue'}
onClick={handleClose}
classes={{
button: classNames({
[classes.button]: true,
[classes.buttonModern]: true,
[classes.marginMedium]: true,
}),
}}
/>
</>
)}
</StyledModalContent>
</Modal>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { identity } from '@quiet/state-manager'
import React from 'react'
import { useSelector } from 'react-redux'
import { useModal } from '../../../containers/hooks'
import { ModalName } from '../../../sagas/modals/modals.types'
import CreateUsernameComponent, { UsernameVariant } from '../../CreateUsername/CreateUsernameComponent'

const UsernameTakenModalContainer = () => {
const usernameTakenModal = useModal(ModalName.usernameTakenModal)

const enterUsername = (username: string) => {
console.log({ username })
}

const user = useSelector(identity.selectors.currentIdentity)

return (
<CreateUsernameComponent
currentUsername={user?.nickname}
registerUsername={enterUsername}
variant={UsernameVariant.TAKEN}
{...usernameTakenModal}
/>
)
}

export default UsernameTakenModalContainer
3 changes: 2 additions & 1 deletion packages/desktop/src/renderer/sagas/modals/modals.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export class ModalsInitialState {
[ModalName.loadingPanel] = { open: true, args: {} }; // Loading modal is open by default and closes on websocket connection
[ModalName.channelCreationModal] = { open: false, args: {} };
[ModalName.unregisteredUsernameModal] = { open: false, args: {} };
[ModalName.duplicatedUsernameModal] = { open: false, args: {} }
[ModalName.duplicatedUsernameModal] = { open: false, args: {} };
[ModalName.usernameTakenModal] = { open: false, args: {} }
}

export const modalsSlice = createSlice({
Expand Down
1 change: 1 addition & 0 deletions packages/desktop/src/renderer/sagas/modals/modals.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ export enum ModalName {
channelCreationModal = 'channelCreationModal',
duplicatedUsernameModal = 'duplicatedUsernameModal',
unregisteredUsernameModal = 'unregisteredUsernameModal',
usernameTakenModal = 'usernameTakenModal',
}
4 changes: 4 additions & 0 deletions packages/mobile/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ import { RootStackParamList } from './route.params'
import ConnectionProcessScreen from './screens/ConnectionProcess/ConnectionProcess.screen'
import { DuplicatedUsernameScreen } from './screens/DuplicatedUsername/DuplicatedUsername.screen'
import { UnregisteredUsernameScreen } from './screens/UnregisteredUsername/UnregisteredUsername.screen'
import UsernameTakenScreen from './screens/UsernameTaken/UsernameTaken.screen'
import NewUsernameRequestedScreen from './screens/NewUsernameRequested/NewUsernameRequested.screen'

LogBox.ignoreAllLogs()

Expand Down Expand Up @@ -103,6 +105,8 @@ function App(): JSX.Element {
<Screen component={ErrorScreen} name={ScreenNames.ErrorScreen} />
<Screen component={DuplicatedUsernameScreen} name={ScreenNames.DuplicatedUsernameScreen} />
<Screen component={UnregisteredUsernameScreen} name={ScreenNames.UnregisteredUsernameScreen} />
<Screen component={UsernameTakenScreen} name={ScreenNames.UsernameTakenScreen} />
<Screen component={NewUsernameRequestedScreen} name={ScreenNames.NewUsernameRequestedScreen} />
</Navigator>
<CommunityContextMenu />
<ChannelContextMenu />
Expand Down
Loading