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

feat(WikiSettings): Add QuestyCaptcha card to Wiki Settings #760

Merged
merged 30 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
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 Dec 8, 2023
cb2c71f
fix: remove dropdown arrow from combobox
dati18 Dec 19, 2023
ae1ff28
fix: move default questions to store
dati18 Dec 22, 2023
b29f8cf
feat: add error messages for empty fields
dati18 Jan 4, 2024
3ba9b1c
fix: Fix recover default questions button
dati18 Jan 8, 2024
77f2c51
clean up testing codes
dati18 Jan 8, 2024
132994e
fix: Fix validation
dati18 Jan 9, 2024
5c1fbbc
fix: fix some specs and behaviors to match design
dati18 Jan 10, 2024
0af47f9
fix: passing wikiId prop to fix saving issue in prod
dati18 Jan 10, 2024
3096695
fix: delete QA bundle before saving
dati18 Jan 10, 2024
cde17ae
fix: add more condition to delete empty QA bundle with save button
dati18 Jan 10, 2024
fb1de61
fix: fix saving behavior
dati18 Jan 11, 2024
2261d3e
fix: fix UI specs
dati18 Jan 11, 2024
fad3f5e
change: reduced answers input field width to 95%
dati18 Jan 11, 2024
1da694a
fix: change answer field margin
dati18 Jan 11, 2024
909d581
fix: remove some unwanted css used inline css
dati18 Jan 12, 2024
1dd0255
fix: change input field width conditionally
dati18 Jan 12, 2024
f3ff79d
Fix missing "panel" property error
AndrewKostka Jan 16, 2024
4c00bcb
Display messages even if the panel is collapsed
AndrewKostka Jan 16, 2024
3f24b32
Switch to inline validation for v-combobox
AndrewKostka Jan 16, 2024
9a7fd67
Simplify showing the delete button
AndrewKostka Jan 16, 2024
c4e7526
Reuse the existing v-snackbar
AndrewKostka Jan 16, 2024
7ff3442
Minor documentation cleanup
AndrewKostka Jan 16, 2024
ec228ae
Switch to using async/await
AndrewKostka Jan 16, 2024
bf6cf41
Fix fetching of existing questions
AndrewKostka Jan 19, 2024
c9c8149
Scroll to first invalid field on error
AndrewKostka Jan 19, 2024
e10cb35
Synchronously update local settings
AndrewKostka Jan 19, 2024
1435654
Captcha toggle should work independently
AndrewKostka Jan 19, 2024
48fcb89
Fix linting errors
AndrewKostka Jan 19, 2024
d4f3143
Fix hidden validation errors
AndrewKostka Jan 19, 2024
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
242 changes: 242 additions & 0 deletions src/components/Pages/ManageWiki/Cards/QuestyCaptcha.vue
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">
Copy link
Contributor

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 (called panel that's not there) see the error in the browser console:

[Vue warn]: Property or method "panel" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.

found in

---> <QuestyCaptcha> at src/components/Pages/ManageWiki/Cards/QuestyCaptcha.vue
       <VMain>
         <ManageWiki> at src/components/Pages/ManageWiki/Tabs/ManageWiki.vue
           <VTabItem>
             <VTabsItems>
               <TabSettings> at src/components/Pages/ManageWiki/TabSettings.vue
                 <VApp>
                   <App> at src/App.vue

Copy link
Contributor Author

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)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

<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>
7 changes: 7 additions & 0 deletions src/components/Pages/ManageWiki/Tabs/ManageWiki.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
<Logo :wikiId="this.wikiId"/>
</v-col>
</v-row>
<v-row>
<v-col>
<QuestyCaptcha :wikiId="this.wikiId"/>
</v-col>
</v-row>
</v-col>
<!--Col 2-->
<v-col>
Expand Down Expand Up @@ -62,10 +67,12 @@ import Skin from '~/components/Pages/ManageWiki/Cards/Skin'
import Registration from '~/components/Pages/ManageWiki/Cards/Registration'
import Wikibase from '~/components/Pages/ManageWiki/Cards/Wikibase'
import Delete from '~/components/Pages/ManageWiki/Cards/Delete'
import QuestyCaptcha from '../Cards/QuestyCaptcha'

export default {
name: 'ManageWiki',
components: {
QuestyCaptcha,
Details,
Logo,
Skin,
Expand Down
31 changes: 30 additions & 1 deletion src/store/wikis.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,20 @@ const mutations = {
const defaultMapping = { properties: { P31: MAPPING_SUGGESTION_PLACEHOLDER, P279: MAPPING_SUGGESTION_PLACEHOLDER }, items: {} }
const wikibaseManifestEquivEntities = entityMappingSetting ? JSON.parse(entityMappingSetting.value) : defaultMapping

const wwUseQuestyCaptchaSetting = details.public_settings.find(setting => setting.name === 'wwUseQuestyCaptcha')
const wwUseQuestyCaptcha = wwUseQuestyCaptchaSetting ? parseInt(wwUseQuestyCaptchaSetting.value) === 1 : false

const captchaQuestionsSetting = details.public_settings.find(setting => setting.name === 'wwCaptchaQuestions')
const defaultQuestions = [
{ question: 'How many vowels are in this question?', answers: ['12', 'twelve'] },
{ question: 'What is the chemical formula of water?', answers: ['H2O'] },
{ question: '2 + 4 = ?', answers: ['6', 'six'] }
]
const captchaQuestions = captchaQuestionsSetting
? Object.entries(JSON.parse(captchaQuestionsSetting.value)).map(([key, value]) => {
return { question: key, answers: value }
}) : undefined

const federatedPropertiesSetting = details.public_settings.find(setting => setting.name === 'wikibaseFedPropsEnable')
const wikibaseFedPropsEnable = federatedPropertiesSetting ? parseInt(federatedPropertiesSetting.value) === 1 : false

Expand Down Expand Up @@ -90,7 +104,10 @@ const mutations = {
wwWikibaseStringLengthString,
wwWikibaseStringLengthMonolingualText,
wwWikibaseStringLengthMultilang,
wwExtEnableConfirmAccount
wwExtEnableConfirmAccount,
wwUseQuestyCaptcha,
captchaQuestions,
defaultQuestions
}
},
clear_current_wiki_settings (state) {
Expand Down Expand Up @@ -119,6 +136,12 @@ const mutations = {
},
set_enable_confirm_account (state, { value }) {
state.currentWikiSettings.wwExtEnableConfirmAccount = value
},
set_enable_questy_captcha (state, { value }) {
state.currentWikiSettings.wwUseQuestyCaptcha = value
},
set_questy_captcha_questions (state, value) {
state.currentWikiSettings.captchaQuestions = value
}
}

Expand Down Expand Up @@ -186,6 +209,12 @@ const actions = {
items: filterOutPlaceholderMapping(mapping.items)
})
})
},
setEnabledQuestyCaptcha ({ commit }, enabled) {
commit('set_enable_questy_captcha', enabled)
},
setQuestyCaptchaQuestions ({ commit }, value) {
commit('set_questy_captcha_questions', value)
}
}

Expand Down
Loading