-
Notifications
You must be signed in to change notification settings - Fork 7
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
feat(WikiSettings): Add QuestyCaptcha card to Wiki Settings #760
Merged
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
669461f
feat(WikiSettings): Add QuestyCaptcha card to Wiki Settings
dati18 cb2c71f
fix: remove dropdown arrow from combobox
dati18 ae1ff28
fix: move default questions to store
dati18 b29f8cf
feat: add error messages for empty fields
dati18 3ba9b1c
fix: Fix recover default questions button
dati18 77f2c51
clean up testing codes
dati18 132994e
fix: Fix validation
dati18 5c1fbbc
fix: fix some specs and behaviors to match design
dati18 0af47f9
fix: passing wikiId prop to fix saving issue in prod
dati18 3096695
fix: delete QA bundle before saving
dati18 cde17ae
fix: add more condition to delete empty QA bundle with save button
dati18 fb1de61
fix: fix saving behavior
dati18 2261d3e
fix: fix UI specs
dati18 fad3f5e
change: reduced answers input field width to 95%
dati18 1da694a
fix: change answer field margin
dati18 909d581
fix: remove some unwanted css used inline css
dati18 1dd0255
fix: change input field width conditionally
dati18 f3ff79d
Fix missing "panel" property error
AndrewKostka 4c00bcb
Display messages even if the panel is collapsed
AndrewKostka 3f24b32
Switch to inline validation for v-combobox
AndrewKostka 9a7fd67
Simplify showing the delete button
AndrewKostka c4e7526
Reuse the existing v-snackbar
AndrewKostka 7ff3442
Minor documentation cleanup
AndrewKostka ec228ae
Switch to using async/await
AndrewKostka bf6cf41
Fix fetching of existing questions
AndrewKostka c9c8149
Scroll to first invalid field on error
AndrewKostka e10cb35
Synchronously update local settings
AndrewKostka 1435654
Captcha toggle should work independently
AndrewKostka 48fcb89
Fix linting errors
AndrewKostka d4f3143
Fix hidden validation errors
AndrewKostka File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
242 changes: 242 additions & 0 deletions
242
src/components/Pages/ManageWiki/Cards/QuestyCaptcha.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,242 @@ | ||
<template> | ||
<v-card> | ||
<v-card-title>Additional Spam Protection</v-card-title> | ||
<v-card-text class="pb-2"> | ||
QuestyCaptcha offers an extra layer of protection against spam accounts. During account creation, users will have to answer a question, which can be defined in settings. For more information on QuestyCaptcha, please visit the documentation page | ||
</v-card-text> | ||
<v-col class="switch"> | ||
<v-switch | ||
label="Activate spam protection" | ||
v-model="isCaptchaActive" | ||
@change="toggleCaptcha" | ||
:loading="waitForToggleUpdate" | ||
:disabled="waitForToggleUpdate" | ||
/> | ||
</v-col> | ||
<v-expansion-panels v-model="panel"> | ||
<v-expansion-panel> | ||
<v-expansion-panel-header> | ||
<strong>SETTINGS</strong> | ||
</v-expansion-panel-header> | ||
<v-expansion-panel-content> | ||
<strong>Guidelines for creating questions and answers:</strong><br/> | ||
<ul> | ||
<li>Keep in mind that users have a wide variety of knowledge and abilities. We therefore recommend three questions at minimum, each requiring different abilities or knowledge.</li> | ||
<li>Consider cultural bias. Use questions that rely on universal knowledge or knowledge related to your wiki.</li> | ||
<li>Keep the answers short and simple. Ideally, try to use questions with only one possible answer.</li> | ||
</ul> | ||
<v-form ref="questyForm"> | ||
<div class="pt-10" v-for="(entry, index) in questionsFromStore" :key="index"> | ||
Question | ||
<v-text-field | ||
class="trash-icon pb-2" | ||
v-model="entry.question" | ||
outlined | ||
hide-details="auto" | ||
:append-outer-icon="showDeleteButton ? 'mdi-delete-outline' : undefined" | ||
:rules="[() => !!entry.question || 'Field cannot be empty. Please provide a question.']" | ||
@click:append-outer="removeQuestion(index)" | ||
dense | ||
:disabled="waitForQuestionsUpdate" | ||
></v-text-field> | ||
Answer | ||
<v-combobox | ||
:class="{'answer-box': true, 'answer-input-field': showDeleteButton}" | ||
v-model="entry.answers" | ||
:items="entry.answers" | ||
multiple | ||
outlined | ||
:rules="[() => !!entry.answers.length || 'Field cannot be empty. Please provide an answer.']" | ||
hide-selected | ||
hide-details="auto" | ||
dense | ||
:disabled="waitForQuestionsUpdate" | ||
> | ||
<template v-slot:selection="{ item }" > | ||
<v-chip class="chips" :disabled="waitForQuestionsUpdate"> | ||
<span class="pr-1"> | ||
{{ item }} | ||
</span> | ||
<v-icon | ||
small | ||
@click="removeAnswer(entry, item)" | ||
:disabled="waitForQuestionsUpdate" | ||
> | ||
mdi-close-circle | ||
</v-icon> | ||
</v-chip> | ||
</template> | ||
</v-combobox> | ||
</div> | ||
<div class="d-flex pb-12 pt-10"> | ||
<v-btn @click="addQuestion" :disabled="waitForQuestionsUpdate" elevation=0 plain class="ml-auto">+ ADD QUESTION</v-btn> | ||
</div> | ||
<div> | ||
<v-btn @click="saveForm" :disabled="waitForQuestionsUpdate" color="primary" width="100%">SAVE QUESTIONS</v-btn> | ||
</div> | ||
<div class="pt-4"> | ||
<v-btn @click="recoverDefaultQuestions" :disabled="waitForQuestionsUpdate" elevation=0 width="100%">RECOVER DEFAULT QUESTIONS</v-btn> | ||
</div> | ||
</v-form> | ||
</v-expansion-panel-content> | ||
</v-expansion-panel> | ||
</v-expansion-panels> | ||
<v-snackbar :color="message.status" elevation="24" v-model="message.show"> | ||
{{ message.text }} | ||
<template v-slot:action> | ||
<v-btn | ||
text | ||
variant="text" | ||
@click="message.show = false" | ||
> | ||
Close | ||
</v-btn> | ||
</template> | ||
</v-snackbar> | ||
</v-card> | ||
</template> | ||
|
||
<script> | ||
export default { | ||
name: 'QuestyCaptcha', | ||
props: [ | ||
'wikiId' | ||
], | ||
data () { | ||
return { | ||
message: false, | ||
questionsFromStore: [], | ||
defaultQuestions: [], | ||
isCaptchaActive: false, | ||
hasNoQuestions: false, | ||
panel: false, | ||
waitForToggleUpdate: false, | ||
waitForQuestionsUpdate: false | ||
} | ||
}, | ||
computed: { | ||
showDeleteButton: function () { | ||
return this.questionsFromStore.length > 1 | ||
} | ||
}, | ||
created () { | ||
this.defaultQuestions = this.$store.state.wikis.currentWikiSettings.defaultQuestions | ||
this.isCaptchaActive = this.$store.state.wikis.currentWikiSettings.wwUseQuestyCaptcha | ||
this.questionsFromStore = this.$store.state.wikis.currentWikiSettings.captchaQuestions | ||
this.hasNoQuestions = !this.questionsFromStore | ||
if (this.hasNoQuestions) { | ||
this.recoverDefaultQuestions() | ||
} | ||
}, | ||
methods: { | ||
removeAnswer (question, answer) { | ||
const index = question.answers.indexOf(answer) | ||
if (index !== -1) { | ||
question.answers.splice(index, 1) | ||
} | ||
}, | ||
removeQuestion (index) { | ||
this.questionsFromStore.splice(index, 1) | ||
}, | ||
addQuestion () { | ||
this.questionsFromStore.push({ | ||
question: '', | ||
answers: [] | ||
}) | ||
}, | ||
showMessage (status, message) { | ||
this.message = { status: status, text: message, show: true } | ||
}, | ||
formatQuestionsForApi (questions) { | ||
return JSON.stringify(questions.reduce((out, entry) => { | ||
out[entry.question] = entry.answers | ||
return out | ||
}, {})) | ||
}, | ||
async toggleCaptcha (enabled) { | ||
try { | ||
this.waitForToggleUpdate = true | ||
if (enabled && this.hasNoQuestions) { | ||
await this.$store.dispatch('updateSetting', { | ||
wiki: this.wikiId, setting: 'wwCaptchaQuestions', value: this.formatQuestionsForApi(this.defaultQuestions) | ||
}) | ||
await this.$store.dispatch('setQuestyCaptchaQuestions', this.defaultQuestions) | ||
this.hasNoQuestions = false | ||
} | ||
await this.$store.dispatch('updateSetting', { wiki: this.wikiId, setting: 'wwUseQuestyCaptcha', value: enabled }) | ||
await this.$store.dispatch('setEnabledQuestyCaptcha', enabled) | ||
this.showMessage('success', `QuestyCaptcha has been successfully ${enabled ? 'enabled' : 'disabled'}.`) | ||
} catch (error) { | ||
console.log(error.response) | ||
this.showMessage('error', `Something went wrong while ${enabled ? 'enabling' : 'disabling'} QuestyCaptcha. Please try again.`) | ||
await this.$nextTick() | ||
this.isCaptchaActive = !enabled | ||
} finally { | ||
this.waitForToggleUpdate = false | ||
} | ||
}, | ||
async saveForm () { | ||
this.waitForQuestionsUpdate = true | ||
|
||
for (let i = 0; i < this.questionsFromStore.length; i++) { | ||
const entry = this.questionsFromStore[i] | ||
const noQuestion = entry.question.trim() === '' | ||
const noAnswer = entry.answers && entry.answers.length === 0 | ||
if (noQuestion && noAnswer && this.questionsFromStore.length > 1) { | ||
this.questionsFromStore.splice(i, 1) | ||
} | ||
} | ||
|
||
await this.$nextTick() | ||
this.$refs.questyForm.validate() | ||
const invalidField = this.$refs.questyForm.$children.find((field) => { | ||
return typeof field.validate === 'function' && !field.validate() | ||
}) | ||
if (invalidField) { | ||
invalidField.$el.scrollIntoView({ behavior: 'smooth' }) | ||
this.waitForQuestionsUpdate = false | ||
return | ||
} | ||
|
||
try { | ||
await this.$store.dispatch('updateSetting', { | ||
wiki: this.wikiId, setting: 'wwCaptchaQuestions', value: this.formatQuestionsForApi(this.questionsFromStore) | ||
}) | ||
await this.$store.dispatch('setQuestyCaptchaQuestions', this.questionsFromStore) | ||
this.showMessage('success', 'Your questions have been saved.') | ||
this.hasNoQuestions = false | ||
this.panel = false | ||
} catch (error) { | ||
console.log(error.response) | ||
this.showMessage('error', 'Something went wrong with saving your questions. Please try again.') | ||
} finally { | ||
this.waitForQuestionsUpdate = false | ||
} | ||
}, | ||
recoverDefaultQuestions () { | ||
// parse() and stringify() are being used to make a copy | ||
this.questionsFromStore = JSON.parse(JSON.stringify(this.defaultQuestions)) | ||
} | ||
} | ||
} | ||
</script> | ||
|
||
<style lang="css" scoped> | ||
AndrewKostka marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.switch { | ||
padding-left: 20px; | ||
padding-bottom: 0; | ||
padding-top: 0; | ||
} | ||
.answer-input-field { | ||
margin-right: 33px !important; | ||
} | ||
>>> .answer-box .v-input__append-inner { | ||
display: none !important; | ||
} | ||
.chips { | ||
margin: 0 8px 0 0 !important; | ||
} | ||
>>> .trash-icon .v-input__append-outer { | ||
margin-top: 0 !important; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the reason nothing is working; is this
v-model
new? Right now its referencing a thing (calledpanel
that's not there) see the error in the browser console:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, it was added there to collapse the panel when saving is done (as Charlie said).
https://github.com/wbstack/ui/pull/760/files#:~:text=%7D)-,this.panel%20%3D%20false,-%7D)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done