Skip to content

Commit

Permalink
Merge pull request #10467 from nextcloud/feat/855/set-username-for-guest
Browse files Browse the repository at this point in the history
Add a welcome window to set username for guests
  • Loading branch information
DorraJaouad authored Sep 12, 2023
2 parents 99823ed + 168bb5d commit 7736c08
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 26 deletions.
9 changes: 9 additions & 0 deletions src/components/ChatView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
@dragover.prevent="handleDragOver"
@dragleave.prevent="handleDragLeave"
@drop.prevent="handleDropFiles">
<GuestWelcomeWindow v-if="isGuestWithoutDisplayName" :token="token" />
<TransitionWrapper name="slide-up" mode="out-in">
<div v-show="isDraggingOver"
class="dragover">
Expand Down Expand Up @@ -70,6 +71,7 @@ import ChevronDoubleDown from 'vue-material-design-icons/ChevronDoubleDown.vue'

import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'

import GuestWelcomeWindow from './GuestWelcomeWindow.vue'
import MessagesList from './MessagesList/MessagesList.vue'
import NewMessage from './NewMessage/NewMessage.vue'
import TransitionWrapper from './TransitionWrapper.vue'
Expand All @@ -87,6 +89,7 @@ export default {
MessagesList,
NewMessage,
TransitionWrapper,
GuestWelcomeWindow,
},

props: {
Expand All @@ -109,6 +112,12 @@ export default {
isGuest() {
return this.$store.getters.getActorType() === 'guests'
},

isGuestWithoutDisplayName() {
const userName = this.$store.getters.getDisplayName()
return !userName && this.isGuest
},

dropHintText() {
if (this.isGuest) {
return t('spreed', 'You need to be logged in to upload files')
Expand Down
156 changes: 156 additions & 0 deletions src/components/GuestWelcomeWindow.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<!--
- @copyright Copyright (c) 2023, Dorra Jaouad <dorra.jaoued1@gmail.com>
-
- @author Dorra Jaouad <dorra.jaoued1@gmail.com>
-
- @license AGPL-3.0-or-later
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->

<template>
<NcModal :container="container"
:can-close="false"
:close-on-click-outside="false"
size="small">
<div class="modal__content">
<div class="conversation-information">
<ConversationIcon :item="conversation"
:show-user-status="false"
disable-menu />
<h2>{{ conversationDisplayName }}</h2>
</div>
<p class="description">
{{ conversationDescription }}
</p>

<label for="textField">{{ t('spreed', 'Enter your name') }}</label>
<NcTextField id="textField"
:value.sync="guestUserName"
:placeholder="t('spreed', 'Guest')"
class="username-form__input"
:show-trailing-button="false"
label-outside
@keydown.enter="handleChooseUserName" />

<NcButton class="submit-button"
type="primary"
:disabled="invalidGuestUsername"
@click="handleChooseUserName">
{{ t('spreed', 'Submit name and join') }}
<template #icon>
<Check :size="20" />
</template>
</NcButton>
</div>
</NcModal>
</template>

<script>
import Check from 'vue-material-design-icons/CheckBold.vue'

import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'

import ConversationIcon from './ConversationIcon.vue'

import { useGuestNameStore } from '../stores/guestName.js'

export default {
name: 'GuestWelcomeWindow',

components: {
NcModal,
NcTextField,
ConversationIcon,
NcButton,
Check,
},

props: {
token: {
type: String,
required: true,
},
},

setup() {
const guestNameStore = useGuestNameStore()
return { guestNameStore }
},

data() {
return {
guestUserName: '',
}
},

computed: {
container() {
return this.$store.getters.getMainContainerSelector()
},

conversation() {
return this.$store.getters.conversation(this.token)
},

conversationDisplayName() {
return this.conversation?.displayName
},

conversationDescription() {
return this.conversation?.description
},

invalidGuestUsername() {
return this.guestUserName.trim() === ''
},
},

methods: {
handleChooseUserName() {
this.guestNameStore.submitGuestUsername(this.token, this.guestUserName)
},
},
}
</script>

<style lang="scss" scoped>
.modal__content {
padding: calc(var(--default-grid-baseline) * 4);
background-color: var(--color-main-background);
margin: 0px 12px;
}

.conversation-information {
margin-top: 5px;
display: flex;
flex-direction: column;
align-items: center;
}

.description {
margin-bottom: 12px;
}

.username-form__input {
margin-bottom: 20px;
}

.submit-button {
margin: 0 auto;
}
</style>
8 changes: 8 additions & 0 deletions src/components/LobbyScreen.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

<template>
<div class="lobby">
<GuestWelcomeWindow v-if="isGuestWithoutDisplayName" :token="token" />
<div class="lobby emptycontent">
<Lobby :size="64" />
<h2>{{ currentConversationName }}</h2>
Expand Down Expand Up @@ -52,6 +53,7 @@ import moment from '@nextcloud/moment'

import NcRichText from '@nextcloud/vue/dist/Components/NcRichText.js'

import GuestWelcomeWindow from './GuestWelcomeWindow.vue'
import Lobby from './missingMaterialDesignIcons/Lobby.vue'
import SetGuestUsername from './SetGuestUsername.vue'

Expand All @@ -63,6 +65,7 @@ export default {
SetGuestUsername,
NcRichText,
Lobby,
GuestWelcomeWindow,
},

computed: {
Expand Down Expand Up @@ -107,6 +110,11 @@ export default {
currentUserIsGuest() {
return !this.$store.getters.getUserId()
},

isGuestWithoutDisplayName() {
const userName = this.$store.getters.getDisplayName()
return !userName && this.currentUserIsGuest
},
},

}
Expand Down
34 changes: 8 additions & 26 deletions src/components/SetGuestUsername.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ import Pencil from 'vue-material-design-icons/Pencil.vue'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'

import { setGuestUserName } from '../services/participantsService.js'
import { useGuestNameStore } from '../stores/guestName.js'

export default {
Expand Down Expand Up @@ -104,6 +103,11 @@ export default {
this.delayHandleUserNameFromBrowserStorage = false
}
},
// Update the input field text
actorDisplayName(newName) {
this.guestUserName = newName
},

},

mounted() {
Expand All @@ -123,31 +127,9 @@ export default {
},

methods: {
async handleChooseUserName() {
const previousName = this.$store.getters.getDisplayName()
try {
this.$store.dispatch('setDisplayName', this.guestUserName)
this.guestNameStore.addGuestName({
token: this.token,
actorId: this.$store.getters.getActorId(),
actorDisplayName: this.guestUserName,
}, { noUpdate: false })
await setGuestUserName(this.token, this.guestUserName)
if (this.guestUserName !== '') {
localStorage.setItem('nick', this.guestUserName)
} else {
localStorage.removeItem('nick')
}
this.isEditingUsername = false
} catch (exception) {
this.$store.dispatch('setDisplayName', previousName)
this.guestNameStore.addGuestName({
token: this.token,
actorId: this.$store.getters.getActorId(),
actorDisplayName: previousName,
}, { noUpdate: false })
console.debug(exception)
}
handleChooseUserName() {
this.guestNameStore.submitGuestUsername(this.token, this.guestUserName)
this.isEditingUsername = false
},

handleEditUsername() {
Expand Down
78 changes: 78 additions & 0 deletions src/stores/__tests__/guestName.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { createPinia, setActivePinia } from 'pinia'

import { setGuestUserName } from '../../services/participantsService.js'
import vuexStore from '../../store/index.js'
import { generateOCSErrorResponse } from '../../test-helpers.js'
import { useGuestNameStore } from '../guestName.js'

jest.mock('../../services/participantsService', () => ({
setGuestUserName: jest.fn(),
}))

describe('guestNameStore', () => {
let store

Expand Down Expand Up @@ -135,4 +142,75 @@ describe('guestNameStore', () => {
expect(global.t).toHaveBeenCalledWith('spreed', 'Guest')
})

test('stores the display name when guest submits it', async () => {
// Arrange
const actor1 = {
token: 'token-1',
actorId: 'actor-id1',
actorDisplayName: t('spreed', 'Guest'),
}

vuexStore.dispatch('setCurrentUser', { uid: 'actor-id1' })

const newName = 'actor 1'

// Mock implementation of setGuestUserName
setGuestUserName.mockResolvedValue()

// Act
await store.submitGuestUsername(actor1.token, newName)

// Assert
expect(setGuestUserName).toHaveBeenCalledWith(actor1.token, newName)
expect(localStorage.setItem).toHaveBeenCalledWith('nick', newName)
expect(store.getGuestName('token-1', 'actor-id1')).toBe('actor 1')
expect(vuexStore.getters.getDisplayName()).toBe('actor 1')
})

test('removes display name from local storage when user sumbits an empty new name', async () => {
// Arrange
const actor1 = {
token: 'token-1',
actorId: 'actor-id1',
actorDisplayName: 'actor 1',
}
const newName = ''

vuexStore.dispatch('setCurrentUser', { uid: 'actor-id1' })

// Mock implementation of setGuestUserName
setGuestUserName.mockResolvedValue()

// Act
await store.submitGuestUsername(actor1.token, newName)

// Assert
expect(setGuestUserName).toHaveBeenCalledWith(actor1.token, newName)
expect(localStorage.removeItem).toHaveBeenCalledWith('nick')
})

test('resets to previous display name if there is an error in setting the new one', async () => {
// Arrange
const actor1 = {
token: 'token-1',
actorId: 'actor-id1',
actorDisplayName: 'old actor 1',
}

vuexStore.dispatch('setCurrentUser', { uid: 'actor-id1' })
store.addGuestName(actor1, { noUpdate: false })

const newName = 'actor 1'

// Mock implementation of setGuestUserName
const error = generateOCSErrorResponse({ payload: {}, status: 400 })
setGuestUserName.mockRejectedValue(error)

// Act
await store.submitGuestUsername(actor1.token, newName)

// Assert
expect(setGuestUserName).toHaveBeenCalledWith(actor1.token, newName)
expect(vuexStore.getters.getDisplayName()).toBe(actor1.actorDisplayName)
})
})
Loading

0 comments on commit 7736c08

Please sign in to comment.