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

✨ Voter eligibility check #454

Merged
merged 17 commits into from
Jul 17, 2024
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
8 changes: 8 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,14 @@ angular.module('voting-booth').config(
isDemo: true
}
})
.state('election.booth-eligibility', {
url: '/:id/eligibility',
templateUrl: 'avBooth/booth.html',
controller: "BoothController",
params: {
isEligibility: true
}
})
.state('election.booth-preview', {
url: '/:id/preview-vote',
templateUrl: 'avBooth/booth.html',
Expand Down
1 change: 1 addition & 0 deletions app.less
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
@import "avBooth/booth.less";
@import "avBooth/2questions-conditional-screen-directive/2questions-conditional-screen-directive.less";
@import "avBooth/election-chooser-screen-directive/election-chooser-screen-directive.less";
@import "avBooth/voter-eligibility-screen-directive/voter-eligibility-screen-directive.less";
@import "avBooth/voting-step-directive/voting-step-directive.less";
@import "avBooth/watermark-directive/watermark-directive.less";
@import "avBooth/accordion-option-directive/accordion-option-directive.less";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ <h3 class="election-title" id="election-title-{{ election.event_id }}">{{electio
<button
class="btn btn-lg btn-success-action btn-plain click-to-vote-btn"
ng-if="checkElectionStarted(election.electionData)"
ng-disabled="!canVoteElection(election)"
ng-disabled="!canVoteElection(election) || !canVote"
ng-click="canVoteElection(election) && click(election)"
ng-i18next="avBooth.electionChooserScreen.clickToVoteBtn">
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ angular.module('avUi')
{
if (!scope.canVote) {
console.log("user cannot vote, so ignoring click");
return;
}
if (scope.hasVoted) {
console.log("user has already voted, so ignoring click");
Expand Down
2 changes: 2 additions & 0 deletions avBooth/booth-directive/booth-directive.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
</div>
<div avb-election-chooser-screen ng-if="state == stateEnum.electionChooserScreen">
</div>
<div avb-voter-eligibility-screen ng-if="state == stateEnum.voterEligibilityScreen">
</div>
<div avb-error-screen ng-if="state == stateEnum.errorScreen">
</div>
<div avb-help-screen ng-if="state == stateEnum.helpScreen">
Expand Down
4 changes: 3 additions & 1 deletion avBooth/booth-directive/booth-directive.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ angular.module('avBooth')
scope.isDemo = (attrs.isDemo === "true");
scope.isPreview = (attrs.isPreview === "true");
scope.isUuidPreview = (attrs.isUuidPreview === "true");
scope.isEligibility = (attrs.isEligibility === "true");
scope.documentation = ConfigService.documentation;
scope.hasSeenStartScreenInThisSession = false;

Expand All @@ -68,7 +69,8 @@ angular.module('avBooth')
castingBallotScreen: 'castingBallotScreen',
successScreen: 'successScreen',
showPdf: 'showPdf',
simultaneousQuestionsV2Screen: 'simultaneousQuestionsV2Screen'
simultaneousQuestionsV2Screen: 'simultaneousQuestionsV2Screen',
voterEligibilityScreen: 'voterEligibilityScreen'
};

// This is used to enable custom css overriding
Expand Down
1 change: 1 addition & 0 deletions avBooth/booth.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
is-preview="{{isPreview}}"
is-uuid-preview="{{isUuidPreview}}"
preview-election="{{previewElection}}"
is-eligibility="{{isEligibility}}"
>
</div>
1 change: 1 addition & 0 deletions avBooth/booth.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ angular
$scope.previewElection = previewElectionParam && decodeURIComponent(previewElectionParam);
$scope.isPreview = $stateParams.isPreview || false;
$scope.isUuidPreview = $stateParams.isUuidPreview || false;
$scope.isEligibility = $stateParams.isEligibility || false;
$scope.electionId = $stateParams.id;
$scope.baseUrl = ConfigService.baseUrl;
$scope.config = $filter('json')(ConfigService);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ angular.module('avBooth')
election.event_id, credentials
);
var canVote = calculateCanVote(elCredentials);
if (canVote) {
scope.canVote = true;
}
var isVoter = calculateIsVoter(elCredentials);
if (
elCredentials &&
Expand Down Expand Up @@ -201,6 +204,13 @@ angular.module('avBooth')

checkDisabled();
scope.chooseElection = chooseElection;
scope.goToVoterEligibility = function () {
scope.setState(scope.stateEnum.voterEligibilityScreen, {});
};

if (scope.isEligibility) {
scope.goToVoterEligibility();
}

scope.showHelp = function () {
$modal.open({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!-- top navbar -->
<a href="#main-content" class="skip-link">{{ 'avBooth.skipLinks.skipToMain' | i18next }}</a>
<a href="#footer" class="skip-link">{{ 'avBooth.skipLinks.skipToFooter' | i18next }}</a>
<div avb-common-header></div>
<div avb-watermark></div>


<h1
class="election-header"
ng-if="election.presentation && election.presentation.voter_eligibility_screen"
ng-bind-html="(election.presentation.voter_eligibility_screen | customI18n : 'title')"
>
</h1>

<div
class="eligibility-description container"
ng-if="election.presentation && election.presentation.voter_eligibility_screen"
ng-bind-html="(election.presentation.voter_eligibility_screen | customI18n : 'description')">
</div>

<main class="container" id="main-content">
<div
av-booth-children-elections
mode="toggle-and-callback"
callback="chooseElection(electionId)"
parent-election-id="{{parentAuthEvent.id}}"
hide-parent="true"
can-vote="false"
has-voted="hasVoted"
children-election-info="childrenElectionInfo"
show-skipped-elections="showSkippedElections"
skipped-elections="skippedElections"
parent-election="parentElection"
></div>
</main>


<div
class="eligibility-footer container"
ng-if="election.presentation && election.presentation.voter_eligibility_screen"
ng-bind-html="(election.presentation.voter_eligibility_screen | customI18n : 'footer')">
</div>

<div avb-common-footer id="footer"></div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/**
* This file is part of voting-booth.
* Copyright (C) 2024 Sequent Tech Inc <[email protected]>

* voting-booth 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.

* voting-booth 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 voting-booth. If not, see <http://www.gnu.org/licenses/>.
**/

angular.module('avBooth')
.directive('avbVoterEligibilityScreen', function($window, $timeout, $q, $modal, ConfigService) {

function link(scope, element, attrs) {
scope.showSkippedElections = false;
scope.organization = ConfigService.organization;
function findElectionCredentials(electionId, credentials) {
return _.find(
credentials,
function (credential) {
return credential.electionId === electionId;
}
);
}

function calculateCanVote(elCredentials) {
return (
!!elCredentials &&
!!elCredentials.token &&
(
elCredentials.numSuccessfulLogins < elCredentials.numSuccessfulLoginsAllowed ||
elCredentials.numSuccessfulLoginsAllowed === 0
)
);
}

function calculateIsVoter(elCredentials) {
return (
!!elCredentials &&
elCredentials.numSuccessfulLoginsAllowed !== -1
);
}

function getElectionCredentials() {
// need to reload in case this changed in success screen..
var credentialsStr = $window.sessionStorage.getItem("vote_permission_tokens");
return JSON.parse(credentialsStr);
}

function isChooserDisabled() {
return (
scope.parentElection &&
scope.parentElection.presentation &&
scope.parentElection.presentation.extra_options &&
!!scope.parentElection.presentation.extra_options.disable__election_chooser_screen
);
}

function generateChildrenInfo() {
var childrenInfo = angular.copy(
scope.parentAuthEvent.children_election_info
);

// need to reload in case this changed in success screen..
var credentials = getElectionCredentials();

// if it's a demo, yes, allow voting by default
scope.canVote = scope.isDemo || scope.isPreview;
scope.hasVoted = false;
scope.skippedElections = [];
childrenInfo.presentation.categories = _.map(
childrenInfo.presentation.categories,
function (category) {
category.events = _.map(
category.events,
function (election) {
var elCredentials = findElectionCredentials(
election.event_id, credentials
);
var canVote = calculateCanVote(elCredentials);
var isVoter = calculateIsVoter(elCredentials);
if (
elCredentials &&
elCredentials.numSuccessfulLogins > 0
) {
scope.hasVoted = true;
}
var retValue = Object.assign(
{},
election,
elCredentials || {},
{
disabled: (!scope.isDemo && !scope.isPreview && !canVote),
hidden: (!scope.isDemo && !scope.isPreview && !isVoter)
}
);
if (!!retValue.skipped) {
scope.skippedElections.push(retValue);
}
return retValue;
}
);
return category;
});
return childrenInfo;
}

function getChildrenElectionsData() {
if (!scope.childrenElectionInfo || isChooserDisabled()) {
return;
}

_.map(
scope.childrenElectionInfo.presentation.categories,
function (category) {
_.map(
category.events,
function (event) {
if (event.hidden) {
return {};
}
return scope.simpleGetElection(event.event_id).then(
function (electionData) {
event.electionData = electionData;
$timeout(function () {
scope.$apply();
});
}
);
}
);
}
);
}

function chooseElection(electionId) {
scope.setState(scope.stateEnum.receivingElection, {});
scope.retrieveElectionConfig(electionId + "");
}

scope.childrenElectionInfo = generateChildrenInfo();
getChildrenElectionsData();

function checkDisabled() {
// if election chooser is disabled and can vote, then go to the first
// election in which it can vote
if (isChooserDisabled()) {
var orderedElectionIds = scope
.childrenElectionInfo
.natural_order;
// If it's a demo booth, do not rely on election credentials
if (scope.isDemo || scope.isPreview) {
scope.increaseDemoElectionIndex();
if (scope.demoElectionIndex < orderedElectionIds.length) {
chooseElection(
orderedElectionIds[scope.demoElectionIndex]
);
} else {
scope.hasVoted = true;
scope.canVote = false;
}
return;
}

var credentials = getElectionCredentials();
for (var i = 0; i < orderedElectionIds.length; i++) {
var electionId = orderedElectionIds[i];
var elCredentials = findElectionCredentials(
electionId,
credentials
);
if (
!elCredentials.skipped &&
!elCredentials.voted &&
calculateCanVote(elCredentials)
) {
chooseElection(electionId);
return;
}
}
// If redirected to no election but there are skipped elections, it
// means that the voter can re-login to vote again so we set the
// showSkippedElections flag
if (scope.skippedElections.length > 0) {
scope.showSkippedElections = true;
}
}
}

checkDisabled();
scope.chooseElection = chooseElection;
scope.goToVoterEligibility = function () {
scope.setState(scope.stateEnum.voterEligibilityScreen, {});
};
}
return {
restrict: 'AE',
scope: true,
link: link,
templateUrl: 'avBooth/voter-eligibility-screen-directive/voter-eligibility-screen-directive.html'
};
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[avb-voter-eligibility-screen] {
}
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
<script src="avBooth/voting-step-directive/voting-step-directive.js" class="app"></script>
<script src="avBooth/accordion-option-directive/accordion-option-directive.js" class="app"></script>
<script src="avBooth/election-chooser-screen-directive/election-chooser-screen-directive.js" class="app"></script>
<script src="avBooth/voter-eligibility-screen-directive/voter-eligibility-screen-directive.js" class="app"></script>
<script src="avBooth/accordion-options-directive/accordion-options-directive.js" class="app"></script>
<script src="avBooth/available-options-directive/available-options-directive.js" class="app"></script>
<script src="avBooth/audit-ballot-screen-directive/audit-ballot-screen-directive.js" class="app"></script>
Expand Down
Loading