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

Add a welcome window to set username for guests #10467

Merged
merged 4 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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"
Antreesy marked this conversation as resolved.
Show resolved Hide resolved
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"
DorraJaouad marked this conversation as resolved.
Show resolved Hide resolved
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;
DorraJaouad marked this conversation as resolved.
Show resolved Hide resolved
}
</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
Loading