Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/polls.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordartcafe <github@dartcafe.de>2020-08-11 22:56:26 +0300
committerdartcafe <github@dartcafe.de>2020-08-11 22:56:26 +0300
commita7d96426e31d8853a2def37c587b27fe1c1ff3f6 (patch)
tree35b9333582f1ad69f97dfc8c7af129a17a4de3d2
parentdad6b97a38348f620d20e5d20108a628269645ee (diff)
user can add own email address
-rw-r--r--appinfo/routes.php1
-rw-r--r--lib/Controller/PollController.php12
-rw-r--r--lib/Controller/ShareController.php24
-rw-r--r--lib/Model/Acl.php40
-rw-r--r--lib/Service/ShareService.php30
-rw-r--r--src/js/App.vue2
-rw-r--r--src/js/components/Base/PersonalLink.vue92
-rw-r--r--src/js/components/Base/PublicRegisterModal.vue (renamed from src/js/components/VoteTable/VoteHeaderPublic.vue)146
-rw-r--r--src/js/components/Create/CreateDlg.vue4
-rw-r--r--src/js/components/SideBar/SideBarTabConfiguration.vue2
-rw-r--r--src/js/components/SideBar/SideBarTabShare.vue4
-rw-r--r--src/js/components/Subscription/Subscription.vue4
-rw-r--r--src/js/store/modules/poll.js13
-rw-r--r--src/js/store/modules/subModules/share.js84
-rw-r--r--src/js/store/modules/subModules/shares.js4
-rw-r--r--src/js/views/Vote.vue40
16 files changed, 348 insertions, 154 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php
index a2ba77ad..b4a23685 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -56,6 +56,7 @@ return [
// ['name' => 'vote#getByToken', 'url' => '/votes/get/s/{token}', 'verb' => 'GET'],
['name' => 'share#add', 'url' => '/share/add', 'verb' => 'POST'],
+ ['name' => 'share#get', 'url' => '/share/{token}', 'verb' => 'GET'],
['name' => 'share#personal', 'url' => '/share/personal', 'verb' => 'POST'],
['name' => 'share#delete', 'url' => '/share/delete/{token}', 'verb' => 'DELETE'],
['name' => 'share#sendInvitation', 'url' => '/share/send/{token}', 'verb' => 'POST'],
diff --git a/lib/Controller/PollController.php b/lib/Controller/PollController.php
index a564fe40..494cb59a 100644
--- a/lib/Controller/PollController.php
+++ b/lib/Controller/PollController.php
@@ -151,8 +151,15 @@ class PollController extends Controller {
}
try {
- $shares = $this->shareService->list($pollId, $token);
+ if ($token) {
+ $share = $this->shareService->get($token);
+ $shares = [];
+ } else {
+ $share = null;
+ $shares = $this->shareService->list($pollId, $token);
+ }
} catch (Exception $e) {
+ $share = null;
$shares = [];
}
@@ -161,8 +168,9 @@ class PollController extends Controller {
'poll' => $poll,
'comments' => $comments,
'options' => $options,
+ 'share' => $share,
'shares' => $shares,
- 'votes' => $votes
+ 'votes' => $votes,
], Http::STATUS_OK);
}
diff --git a/lib/Controller/ShareController.php b/lib/Controller/ShareController.php
index 9cd5f554..0c50b4c7 100644
--- a/lib/Controller/ShareController.php
+++ b/lib/Controller/ShareController.php
@@ -94,6 +94,26 @@ class ShareController extends Controller {
* @param string $userEmail
* @return DataResponse
*/
+ public function get($token) {
+ try {
+ return new DataResponse(['share' => $this->shareService->get($token)], Http::STATUS_CREATED);
+ } catch (NotAuthorizedException $e) {
+ return new DataResponse(['error' => $e->getMessage()], $e->getStatus());
+ } catch (\Exception $e) {
+ return new DataResponse($e, Http::STATUS_CONFLICT);
+ }
+ }
+
+ /**
+ * Add share
+ * @NoAdminRequired
+ * @param int $pollId
+ * @param int $pollId
+ * @param string $type
+ * @param string $userId
+ * @param string $userEmail
+ * @return DataResponse
+ */
public function setEmailAddress($token, $userEmail) {
try {
return new DataResponse(['share' => $this->shareService->setEmailAddress($token, $userEmail)], Http::STATUS_OK);
@@ -115,10 +135,10 @@ class ShareController extends Controller {
* @param string $userName
* @return DataResponse
*/
- public function personal($token, $userName) {
+ public function personal($token, $userName, $emailAddress = '') {
try {
- return new DataResponse($this->shareService->personal($token, $userName), Http::STATUS_CREATED);
+ return new DataResponse($this->shareService->personal($token, $userName, $emailAddress), Http::STATUS_CREATED);
} catch (NotAuthorizedException $e) {
return new DataResponse(['error' => $e->getMessage()], $e->getStatus());
} catch (InvalidUsername $e) {
diff --git a/lib/Model/Acl.php b/lib/Model/Acl.php
index ee7b627b..86743c67 100644
--- a/lib/Model/Acl.php
+++ b/lib/Model/Acl.php
@@ -75,6 +75,9 @@ class Acl implements JsonSerializable {
/** @var Poll */
private $poll;
+ /** @var Share */
+ private $share;
+
/**
* Acl constructor.
* @param string $appName
@@ -84,7 +87,8 @@ class Acl implements JsonSerializable {
* @param PollMapper $pollMapper
* @param VoteMapper $voteMapper
* @param ShareMapper $shareMapper
- * @param Poll $pollMapper
+ * @param Poll $poll
+ * @param Share $share
*
*/
public function __construct(
@@ -94,7 +98,8 @@ class Acl implements JsonSerializable {
PollMapper $pollMapper,
VoteMapper $voteMapper,
ShareMapper $shareMapper,
- Poll $poll
+ Poll $poll,
+ Share $share
) {
$this->userId = $userId;
$this->userManager = $userManager;
@@ -103,6 +108,7 @@ class Acl implements JsonSerializable {
$this->voteMapper = $voteMapper;
$this->shareMapper = $shareMapper;
$this->poll = $poll;
+ $this->share = $share;
}
/**
@@ -117,26 +123,27 @@ class Acl implements JsonSerializable {
$this->token = $token;
$this->pollId = 0;
$this->userId = null;
- $share = $this->shareMapper->findByToken($token);
+ $this->share = $this->shareMapper->findByToken($token);
if (\OC::$server->getUserSession()->isLoggedIn()) {
- if ($share->getType() !== 'group' && $share->getType() !== 'public') {
+ if ($this->share->getType() !== 'group' && $this->share->getType() !== 'public') {
throw new NotAuthorizedException;
}
$this->userId = \OC::$server->getUserSession()->getUser()->getUID();
} else {
- if ($share->getType() === 'group' || $share->getType() === 'user') {
+ if ($this->share->getType() === 'group' || $this->share->getType() === 'user') {
throw new NotAuthorizedException;
}
- $this->userId = $share->getUserId();
+ $this->userId = $this->share->getUserId();
}
- $this->pollId = $share->getPollId();
+ $this->pollId = $this->share->getPollId();
} elseif ($pollId) {
$this->user = \OC::$server->getUserSession()->getUser()->getUID();
$this->pollId = $pollId;
+ $this->share = null;
}
$this->poll = $this->pollMapper->find($this->pollId);
@@ -310,6 +317,16 @@ class Acl implements JsonSerializable {
* @NoAdminRequired
* @return bool
*/
+ public function getAllowSubscribe(): bool {
+ return ($this->hasEmail())
+ && !$this->poll->getDeleted()
+ && $this->getAllowView();
+ }
+
+ /**
+ * @NoAdminRequired
+ * @return bool
+ */
public function getAllowComment(): bool {
return !$this->poll->getDeleted() && boolval($this->userId);
}
@@ -348,6 +365,14 @@ class Acl implements JsonSerializable {
return $this->token;
}
+ private function hasEmail():bool {
+ if ($this->share) {
+ return strlen($this->share->getUserEmail()) > 0;
+ } else {
+ return \OC::$server->getUserSession()->isLoggedIn();
+ }
+ }
+
/**
* @return array
*/
@@ -367,6 +392,7 @@ class Acl implements JsonSerializable {
'allowEdit' => $this->getAllowEdit(),
'allowSeeResults' => $this->getAllowSeeResults(),
'allowSeeUsernames' => $this->getAllowSeeUsernames(),
+ 'allowSubscribe' => $this->getAllowSubscribe(),
'userHasVoted' => $this->getUserHasVoted(),
'groupShare' => $this->getGroupShare(),
'personalShare' => $this->getPersonalShare(),
diff --git a/lib/Service/ShareService.php b/lib/Service/ShareService.php
index 70881cf9..83f6233f 100644
--- a/lib/Service/ShareService.php
+++ b/lib/Service/ShareService.php
@@ -120,6 +120,10 @@ class ShareService {
throw new NotAuthorizedException;
}
+ if ($type === 'contact') {
+ $type = 'external';
+ }
+
$this->share = new Share();
$this->share->setType($type);
$this->share->setPollId($pollId);
@@ -168,19 +172,20 @@ class ShareService {
* @throws NotAuthorizedException
* @throws InvalidUsername
*/
- public function personal($token, $userName) {
- $publicShare = $this->shareMapper->findByToken($token);
+ public function personal($token, $userName, $emailAddress) {
+ $this->share = $this->shareMapper->findByToken($token);
// Return of validatePublicUsername is a DataResponse
- $checkUsername = $this->systemController->validatePublicUsername($publicShare->getPollId(), $userName, $token);
+ $checkUsername = $this->systemController->validatePublicUsername($this->share->getPollId(), $userName, $token);
// if status is not 200, return DataResponse from validatePublicUsername
if ($checkUsername->getStatus() !== 200) {
throw new InvalidUsername;
}
- if ($publicShare->getType() === 'public') {
+ if ($this->share->getType() === 'public') {
+ $pollId = $this->share->getPollId();
$this->share = new Share();
$this->share->setToken(\OC::$server->getSecureRandom()->generate(
16,
@@ -189,17 +194,20 @@ class ShareService {
ISecureRandom::CHAR_UPPER
));
$this->share->setType('external');
- $this->share->setPollId($publicShare->getPollId());
+ $this->share->setPollId($pollId);
$this->share->setUserId($userName);
- $this->share->setUserEmail('');
+ $this->share->setUserEmail($emailAddress);
$this->share->setInvitationSent(time());
- return $this->shareMapper->insert($this->share);
+ $this->shareMapper->insert($this->share);
+ $this->mailService->sendInvitationMail($this->share->getToken());
+ return $this->share;
- } elseif ($publicShare->getType() === 'email') {
+ } elseif ($this->share->getType() === 'email') {
- $publicShare->setType('external');
- $publicShare->setUserId($userName);
- return $this->shareMapper->update($publicShare);
+ $this->share->setType('external');
+ $this->share->setUserId($userName);
+ $this->share->setUserEmail($emailAddress);
+ return $this->shareMapper->update($this->share);
} else {
throw new NotAuthorizedException;
diff --git a/src/js/App.vue b/src/js/App.vue
index 6ce6cc6e..3ca736e1 100644
--- a/src/js/App.vue
+++ b/src/js/App.vue
@@ -285,7 +285,7 @@ input {
padding: 8px;
background-color: var(--color-main-background);
border-radius: var(--border-radius);
- margin: 12px 0;
+ margin: 12px 6px;
}
</style>
diff --git a/src/js/components/Base/PersonalLink.vue b/src/js/components/Base/PersonalLink.vue
new file mode 100644
index 00000000..198f408c
--- /dev/null
+++ b/src/js/components/Base/PersonalLink.vue
@@ -0,0 +1,92 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - 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>
+ <div v-show="share.type !== 'public'">
+ <h2 class="title">
+ {{ t('polls', 'This is a public poll.') }}
+ </h2>
+ <p>{{ t('polls', 'The following link is your personal access to this poll. You can reenter this this poll at any time, change your vote and leave comments.') }}</p>
+ <p>{{ t('polls', 'Your personal link to this poll: {linkURL}', { linkURL: personalLink} ) }}</p>
+ <ButtonDiv icon="icon-clippy" :title="t('polls','Copy this link to the clipboard')" @click="copyLink()" />
+ <ButtonDiv v-if="share.userEmail"
+ icon="icon-mail"
+ :title="t('polls','Resend invitation mail to {emailAdress}', { emailAdress: share.userEmail })"
+ @click="sendInvitation()" />
+ </div>
+</template>
+
+<script>
+import ButtonDiv from '../Base/ButtonDiv'
+import { showSuccess, showError } from '@nextcloud/dialogs'
+import { mapState } from 'vuex'
+
+export default {
+ name: 'PersonalLink',
+
+ components: {
+ ButtonDiv,
+ },
+
+ computed: {
+ ...mapState({
+ share: state => state.poll.share,
+ }),
+
+ personalLink() {
+ return window.location.origin.concat(
+ this.$router.resolve({
+ name: 'publicVote',
+ params: { token: this.$route.params.token },
+ }).href
+ )
+ },
+
+ },
+
+ methods: {
+ sendInvitation() {
+ this.$store.dispatch('poll/share/sendInvitation')
+ .then((response) => {
+ response.data.sentResult.sentMails.forEach((item) => {
+ showSuccess(t('polls', 'Invitation sent to {emailAddress}', { emailAddress: item.eMailAddress }))
+ })
+ response.data.sentResult.abortedMails.forEach((item) => {
+ console.error('Mail could not be sent!', { recipient: item })
+ showError(t('polls', 'Error sending invitation to {emailAddress}', { emailAddress: item.eMailAddress }))
+ })
+ })
+ },
+
+ copyLink() {
+ this.$copyText(this.personalLink).then(
+ function() {
+ OC.Notification.showTemporary(t('polls', 'Link copied to clipboard'), { type: 'success' })
+ },
+ function() {
+ OC.Notification.showTemporary(t('polls', 'Error while copying link to clipboard'), { type: 'error' })
+ }
+ )
+ },
+ },
+}
+</script>
diff --git a/src/js/components/VoteTable/VoteHeaderPublic.vue b/src/js/components/Base/PublicRegisterModal.vue
index 052a7339..03e83655 100644
--- a/src/js/components/VoteTable/VoteHeaderPublic.vue
+++ b/src/js/components/Base/PublicRegisterModal.vue
@@ -21,28 +21,9 @@
-->
<template>
- <div v-if="poll.id" class="vote__header">
- <div v-show="displayLink" class="vote__header__personal-link">
- <ConfigBox :title="t('polls', 'Your personal link to this poll')"
- icon-class="icon-share"
- :info="t('polls','Copy this link for reentering the poll at any time and edit your votes.')">
- <div class="personal-link">
- {{ personalLink }}
- <a class="icon icon-clippy" @click="copyLink()" />
- </div>
- </ConfigBox>
- <ConfigBox :title="t('polls', 'Your registered email address')"
- icon-class="icon-mail"
- :info="t('polls','Enter your email address, to be able to subscribe to updates of this poll.')">
- <div class="user-email">
- {{ userEmail }}
- <a class="icon icon-rename" />
- </div>
- </ConfigBox>
- </div>
-
- <Modal v-show="!isValidUser &!expired & modal" :can-close="false">
- <div class="modal__content">
+ <Modal v-show="modal" :can-close="false">
+ <div class="modal__content">
+ <div class="enter__name">
<h2>{{ t('polls', 'Who are you?') }}</h2>
<p>{{ t('polls', 'To participate, tell us how we can call you!') }}</p>
@@ -56,46 +37,50 @@
<span v-show="!checkingUserName && userName.length > 2 && !isValidName" class="error">{{ t('polls', 'This name is not valid, i.e. because it is already in use.') }}</span>
<span v-show="!checkingUserName && userName.length > 2 && isValidName" class="error">{{ t('polls', 'OK, we will call you {username}.', {username : userName }) }}</span>
</div>
+ </div>
+ <div class="enter__email">
+ <p>{{ t('polls', 'Enter your email address to be able to subscribe to updates and get your personal link via email.') }}</p>
- <div class="modal__buttons">
- <a :href="loginLink" class="modal__buttons__link"> {{ t('polls', 'You have an account? Log in here.') }} </a>
- <div class="modal__buttons__spacer" />
- <ButtonDiv :title="t('polls', 'Cancel')"
- @click="closeModal" />
- <ButtonDiv :primary="true" :disabled="!isValidName || checkingUserName" :title="t('polls', 'OK')"
- @click="writeUserName" />
- </div>
+ <input v-model="emailAddress" :class="{ error: (!isValidName && userName.length > 0), success: isValidName }"
+ type="text"
+ :placeholder="t('polls', 'Enter your email address')" @keyup.enter="writeUserName">
</div>
- </Modal>
- </div>
+
+ <div class="modal__buttons">
+ <a :href="loginLink" class="modal__buttons__link"> {{ t('polls', 'You have an account? Log in here.') }} </a>
+ <div class="modal__buttons__spacer" />
+ <ButtonDiv :title="t('polls', 'Cancel')"
+ @click="closeModal" />
+ <ButtonDiv :primary="true" :disabled="!isValidName || checkingUserName" :title="t('polls', 'OK')"
+ @click="writeUserName" />
+ </div>
+ </div>
+ </Modal>
</template>
<script>
import debounce from 'lodash/debounce'
import axios from '@nextcloud/axios'
-import ConfigBox from '../Base/ConfigBox'
import ButtonDiv from '../Base/ButtonDiv'
import { generateUrl } from '@nextcloud/router'
import { Modal } from '@nextcloud/vue'
-import { mapState, mapGetters } from 'vuex'
+import { mapState } from 'vuex'
export default {
- name: 'VoteHeaderPublic',
+ name: 'PublicRegisterModal',
components: {
Modal,
- ConfigBox,
ButtonDiv,
},
data() {
return {
userName: '',
- token: '',
+ emailAddress: '',
checkingUserName: false,
redirecting: false,
isValidName: false,
- newName: '',
modal: true,
}
},
@@ -103,22 +88,9 @@ export default {
computed: {
...mapState({
poll: state => state.poll,
- acl: state => state.poll.acl,
- }),
-
- ...mapGetters({
- expired: 'poll/expired',
+ share: state => state.poll.share,
}),
- userEmail: {
- get() {
- return this.poll.shares.list[0].userEmail
- },
- set(value) {
- // this.writeValueDebounced({ description: value })
- },
- },
-
loginLink() {
const redirectUrl = this.$router.resolve({
name: 'publicVote',
@@ -126,24 +98,6 @@ export default {
}).href
return generateUrl('login?redirect_url=' + redirectUrl)
},
-
- personalLink() {
- return window.location.origin.concat(
- this.$router.resolve({
- name: 'publicVote',
- params: { token: this.$route.params.token },
- }).href
- )
- },
-
- displayLink() {
- return (this.acl.userId !== '' && this.acl.userId !== null && this.acl.token)
- },
-
- isValidUser() {
- return (this.acl.userId !== '' && this.acl.userId !== null)
- },
-
},
watch: {
@@ -151,16 +105,20 @@ export default {
this.isValidName = false
if (this.userName.length > 2) {
this.checkingUserName = true
- this.isValidName = this.validatePublicUsername()
+ if (this.userName !== this.share.userid) {
+ this.isValidName = this.validatePublicUsername()
+ }
} else {
this.invalidUserNameMessage = t('polls', 'Please use at least 3 characters for your username!')
this.checkingUserName = false
}
},
+ },
- 'poll.id': function(newValue) {
- this.setFocus()
- },
+ mounted() {
+ this.userName = this.share.userId
+ this.emailAddress = this.share.userEmail
+ this.setFocus()
},
methods: {
@@ -170,22 +128,9 @@ export default {
})
},
- showModal() {
- this.modal = true
- },
closeModal() {
this.modal = false
},
- copyLink() {
- this.$copyText(this.personalLink).then(
- function() {
- OC.Notification.showTemporary(t('polls', 'Link copied to clipboard'), { type: 'success' })
- },
- function() {
- OC.Notification.showTemporary(t('polls', 'Error while copying link to clipboard'), { type: 'error' })
- }
- )
- },
validatePublicUsername: debounce(function() {
if (this.userName.length > 2) {
@@ -213,14 +158,15 @@ export default {
writeUserName() {
if (this.isValidName) {
- this.$store.dispatch('poll/shares/addPersonal', { token: this.$route.params.token, userName: this.userName })
+ this.$store.dispatch('poll/shares/addPersonal', { token: this.$route.params.token, userName: this.userName, emailAddress: this.emailAddress })
.then((response) => {
if (this.$route.params.token === response.token) {
this.$store.dispatch({ type: 'poll/get', pollId: this.$route.params.id, token: this.$route.params.token })
+ this.closeModal()
} else {
- this.token = response.token
this.redirecting = true
- this.$router.replace({ name: 'publicVote', params: { token: this.token } })
+ this.$router.replace({ name: 'publicVote', params: { token: response.token } })
+ this.closeModal()
}
})
.catch(() => {
@@ -233,25 +179,11 @@ export default {
</script>
<style lang="scss">
- .vote__header {
- margin: 8px 24px;
- }
- .vote__header__personal-link {
- // display: flex;
- padding: 4px 12px;
- margin: 0 12px 0 24px;
- border: 2px solid var(--color-success);
- background-color: var(--color-background-success) !important;
- border-radius: var(--border-radius);
- font-size: 1.2em;
- opacity: 0.8;
- .icon {
- margin: 0 12px;
+ .modal__content {
+ .enter__name, .enter__email {
+ margin-bottom: 12px;
}
}
- .personal-link, .user-email {
- display: flex;
- }
</style>
diff --git a/src/js/components/Create/CreateDlg.vue b/src/js/components/Create/CreateDlg.vue
index 5cd03f30..f86131c2 100644
--- a/src/js/components/Create/CreateDlg.vue
+++ b/src/js/components/Create/CreateDlg.vue
@@ -103,11 +103,11 @@ export default {
.then((response) => {
emit('update-polls')
this.cancel()
- OC.Notification.showTemporary(t('polls', 'Poll "%n" added', 1, response.data.id), { type: 'success' })
+ OC.Notification.showTemporary(t('polls', 'Poll "{pollTitle}" added', { pollTitle: response.data.id }), { type: 'success' })
this.$router.push({ name: 'vote', params: { id: response.data.id } })
})
.catch(() => {
- OC.Notification.showTemporary(t('polls', 'Error while creating Poll "%n"', 1, this.title), { type: 'error' })
+ OC.Notification.showTemporary(t('polls', 'Error while creating Poll "{pollTitle}"', { pollTitle: this.title }), { type: 'error' })
})
},
diff --git a/src/js/components/SideBar/SideBarTabConfiguration.vue b/src/js/components/SideBar/SideBarTabConfiguration.vue
index 8ca279dc..2e6a3f6f 100644
--- a/src/js/components/SideBar/SideBarTabConfiguration.vue
+++ b/src/js/components/SideBar/SideBarTabConfiguration.vue
@@ -306,7 +306,7 @@ export default {
} else {
this.$store.dispatch('poll/update')
.then((response) => {
- OC.Notification.showTemporary(t('polls', '%n successfully saved', 1, response.data.title), { type: 'success' })
+ OC.Notification.showTemporary(t('polls', '"{pollTitle}" successfully saved', 1, { pollTitle: response.data.title }), { type: 'success' })
emit('update-polls')
})
.catch(() => {
diff --git a/src/js/components/SideBar/SideBarTabShare.vue b/src/js/components/SideBar/SideBarTabShare.vue
index b0ad9d8d..ecf8af56 100644
--- a/src/js/components/SideBar/SideBarTabShare.vue
+++ b/src/js/components/SideBar/SideBarTabShare.vue
@@ -24,7 +24,7 @@
<div>
<ConfigBox v-if="!acl.isOwner" :title="t('polls', 'As an admin you may edit this poll')" icon-class="icon-checkmark" />
- <ConfigBox :title="t('polls', 'Invitations')" icon-class="icon-share">
+ <ConfigBox :title="t('polls', 'Shares')" icon-class="icon-share">
<TransitionGroup :css="false" tag="div" class="shared-list">
<UserItem v-for="(share) in invitationShares"
:key="share.id" v-bind="share"
@@ -80,7 +80,7 @@
<Avatar icon-class="icon-public" :is-no-user="true" />
<!-- <div class="avatar icon-public" /> -->
<div class="share-item__description">
- {{ t('polls', 'Public link (' + share.token + ')') }}
+ {{ t('polls', 'Public link ({token})', {token: share.token }) }}
</div>
</div>
<Actions>
diff --git a/src/js/components/Subscription/Subscription.vue b/src/js/components/Subscription/Subscription.vue
index 78d4a552..d91e8a0a 100644
--- a/src/js/components/Subscription/Subscription.vue
+++ b/src/js/components/Subscription/Subscription.vue
@@ -24,7 +24,8 @@
<div class="subscription">
<input id="subscribe" v-model="subscribe" type="checkbox"
class="checkbox">
- <label for="subscribe">{{ t('polls', 'Receive notification email on activity') }}</label>
+ <label v-if="share.userEmail" for="subscribe">{{ t('polls', 'Receive notification email on activity to {emailAddress}', {emailAddress: share.userEmail}) }}</label>
+ <label v-else for="subscribe">{{ t('polls', 'Receive notification email on activity') }}</label>
</div>
</template>
@@ -37,6 +38,7 @@ export default {
...mapState({
subscription: state => state.subscription,
poll: state => state.poll,
+ share: state => state.poll.share,
}),
subscribe: {
diff --git a/src/js/store/modules/poll.js b/src/js/store/modules/poll.js
index 8bbdb947..3a8b0f31 100644
--- a/src/js/store/modules/poll.js
+++ b/src/js/store/modules/poll.js
@@ -28,6 +28,7 @@ import acl from './subModules/acl.js'
import comments from './subModules/comments.js'
import options from './subModules/options.js'
import shares from './subModules/shares.js'
+import share from './subModules/share.js'
import votes from './subModules/votes.js'
const defaultPoll = () => {
@@ -58,7 +59,9 @@ const modules = {
options: options,
shares: shares,
votes: votes,
+ share: share,
}
+
const mutations = {
set(state, payload) {
Object.assign(state, payload.poll)
@@ -148,6 +151,7 @@ const actions = {
context.commit('comments/reset')
context.commit('options/reset')
context.commit('shares/reset')
+ context.commit('share/reset')
context.commit('votes/reset')
return
}
@@ -158,13 +162,12 @@ const actions = {
context.commit('comments/set', response.data)
context.commit('options/set', response.data)
context.commit('shares/set', response.data)
+ context.commit('share/set', response.data)
context.commit('votes/set', response.data)
return response
- }, (error) => {
- if (error.response.status !== '404' && error.response.status !== '401') {
- console.debug('Error loading poll', { error: error.response }, { payload: payload })
- return error.response
- }
+ })
+ .catch((error) => {
+ console.debug('Error loading poll', { error: error.response }, { payload: payload })
throw error
})
},
diff --git a/src/js/store/modules/subModules/share.js b/src/js/store/modules/subModules/share.js
new file mode 100644
index 00000000..1e92b655
--- /dev/null
+++ b/src/js/store/modules/subModules/share.js
@@ -0,0 +1,84 @@
+/*
+ * @copyright Copyright (c) 2019 Rene Gieling <github@dartcafe.de>
+ *
+ * @author Rene Gieling <github@dartcafe.de>
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * 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/>.
+ *
+ */
+
+import axios from '@nextcloud/axios'
+import { generateUrl } from '@nextcloud/router'
+
+const defaultShares = () => {
+ return {
+ displayName: '',
+ externalUser: false,
+ id: null,
+ invitationSent: 0,
+ pollId: null,
+ token: '',
+ type: '',
+ userEmail: '',
+ userId: '',
+ }
+}
+
+const state = defaultShares()
+
+const namespaced = true
+
+const mutations = {
+ set(state, payload) {
+ Object.assign(state, payload.share)
+ },
+
+ reset(state) {
+ Object.assign(state, defaultShares())
+ },
+
+}
+
+const actions = {
+ get(context, payload) {
+ const endPoint = 'apps/polls/share'
+ return axios.get(generateUrl(endPoint.concat('/', payload.token)))
+ .then((response) => {
+ context.commit('set', { share: response.data.share })
+ return response.data
+ })
+ .catch((error) => {
+ console.error('Error retrieving share', { error: error.response }, { payload: payload })
+ throw error
+ })
+ },
+
+ sendInvitation(context, payload) {
+ const endPoint = 'apps/polls/share/send'
+ return axios.post(generateUrl(endPoint.concat('/', context.state.token)))
+ .then((response) => {
+ context.commit('set', { share: response.data.share })
+ return response
+ })
+ .catch((error) => {
+ console.error('Error sending invitation', { error: error.response }, { payload: payload })
+ throw error
+ })
+ },
+}
+
+export default { namespaced, state, mutations, actions }
diff --git a/src/js/store/modules/subModules/shares.js b/src/js/store/modules/subModules/shares.js
index d3f5b5c7..c684e297 100644
--- a/src/js/store/modules/subModules/shares.js
+++ b/src/js/store/modules/subModules/shares.js
@@ -64,7 +64,7 @@ const getters = {
invitation: state => {
const invitationTypes = ['user', 'group', 'email', 'external', 'contact']
return state.list.filter(share => {
- return invitationTypes.includes(share.type)
+ return invitationTypes.includes(share.type) && share.invitationSent
})
},
@@ -105,7 +105,7 @@ const actions = {
addPersonal(context, payload) {
const endPoint = 'apps/polls/share/personal'
- return axios.post(generateUrl(endPoint), { token: payload.token, userName: payload.userName })
+ return axios.post(generateUrl(endPoint), { token: payload.token, userName: payload.userName, emailAddress: payload.emailAddress })
.then((response) => {
return { token: response.data.token }
})
diff --git a/src/js/views/Vote.vue b/src/js/views/Vote.vue
index 585f8176..0f110a42 100644
--- a/src/js/views/Vote.vue
+++ b/src/js/views/Vote.vue
@@ -62,8 +62,8 @@
</h3>
</div>
- <div class="area__public">
- <VoteHeaderPublic v-if="!getCurrentUser()" />
+ <div v-if="$route.name === 'publicVote' && poll.id" class="area__public">
+ <PersonalLink v-if="share.userId" />
</div>
<div class="area__main">
@@ -80,10 +80,14 @@
</div>
<div class="area__footer">
- <Subscription v-if="getCurrentUser() || (acl.token && poll.shares.list[0].userEmail)" />
+ <Subscription v-if="acl.allowSubscribe" />
<ParticipantsList v-if="acl.allowSeeUsernames" />
</div>
<LoadingOverlay v-if="isLoading" />
+
+ <div v-if="$route.name === 'publicVote' && poll.id">
+ <PublicRegisterModal v-show="!share.userId && !isExpired" />
+ </div>
</AppContent>
</template>
@@ -92,11 +96,13 @@ import { Actions, ActionButton, AppContent } from '@nextcloud/vue'
import Subscription from '../components/Subscription/Subscription'
import Badge from '../components/Base/Badge'
import ParticipantsList from '../components/Base/ParticipantsList'
+import PersonalLink from '../components/Base/PersonalLink'
import PollInformation from '../components/Base/PollInformation'
import LoadingOverlay from '../components/Base/LoadingOverlay'
-import VoteHeaderPublic from '../components/VoteTable/VoteHeaderPublic'
+import PublicRegisterModal from '../components/Base/PublicRegisterModal'
import VoteTable from '../components/VoteTable/VoteTable'
import { mapState, mapGetters } from 'vuex'
+import { getCurrentUser } from '@nextcloud/auth'
import { emit } from '@nextcloud/event-bus'
import moment from '@nextcloud/moment'
@@ -107,11 +113,12 @@ export default {
ActionButton,
AppContent,
Badge,
- Subscription,
+ LoadingOverlay,
ParticipantsList,
+ PersonalLink,
PollInformation,
- LoadingOverlay,
- VoteHeaderPublic,
+ PublicRegisterModal,
+ Subscription,
VoteTable,
},
@@ -130,6 +137,7 @@ export default {
poll: state => state.poll,
acl: state => state.poll.acl,
options: state => state.poll.options.list,
+ share: state => state.poll.share,
}),
...mapGetters({
@@ -181,8 +189,19 @@ export default {
},
created() {
- this.loadPoll()
- emit('toggle-sidebar', { open: (window.innerWidth > 920) })
+ if (getCurrentUser() && this.$route.params.token) {
+ // reroute to the internal vote page, if the user is logged in
+ this.$store.dispatch('poll/shares/get', { token: this.$route.params.token })
+ .then((response) => {
+ this.$router.replace({ name: 'vote', params: { id: response.share.pollId } })
+ })
+ .catch(() => {
+ this.$router.replace({ name: 'notfound' })
+ })
+ } else {
+ this.loadPoll()
+ emit('toggle-sidebar', { open: (window.innerWidth > 920) })
+ }
},
beforeDestroy() {
@@ -210,8 +229,7 @@ export default {
this.isLoading = false
window.document.title = this.windowTitle
})
- .catch((error) => {
- console.error(error)
+ .catch(() => {
this.isLoading = false
this.$router.replace({ name: 'notfound' })
})