diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2c2861b579..5bda31163a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,6 +15,8 @@ "@tiptap/extension-placeholder": "^2.0.0-beta.199", "@tiptap/starter-kit": "^2.0.0-beta.199", "@tiptap/vue-3": "^2.0.0-beta.199", + "@vuelidate/core": "^2.0.0", + "@vuelidate/validators": "^2.0.0", "@vueuse/core": "^9.7.0", "apollo-boost": "^0.4.9", "axios": "^0.22.0", @@ -4055,6 +4057,90 @@ "integrity": "sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==", "dev": true }, + "node_modules/@vuelidate/core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@vuelidate/core/-/core-2.0.0.tgz", + "integrity": "sha512-xIFgdQlScO0aaSZ0wTGPJh8YcTMNAj5veI8yPgiAyxOT+GV7vNQFiU1vpYWCL4cklkkhYvRRSC2OEX7YOZNmPQ==", + "dependencies": { + "vue-demi": "^0.13.11" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^2.0.0 || >=3.0.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vuelidate/core/node_modules/vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vuelidate/validators": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@vuelidate/validators/-/validators-2.0.0.tgz", + "integrity": "sha512-fQQcmDWfz7pyH5/JPi0Ng2GEgNK1pUHn/Z/j5rG/Q+HwhgIXvJblTPcZwKOj1ABL7V4UVuGKECvZCDHNGOwdrg==", + "dependencies": { + "vue-demi": "^0.13.11" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^2.0.0 || >=3.0.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vuelidate/validators/node_modules/vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@vueuse/core": { "version": "9.7.0", "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.7.0.tgz", @@ -18790,6 +18876,38 @@ "integrity": "sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==", "dev": true }, + "@vuelidate/core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@vuelidate/core/-/core-2.0.0.tgz", + "integrity": "sha512-xIFgdQlScO0aaSZ0wTGPJh8YcTMNAj5veI8yPgiAyxOT+GV7vNQFiU1vpYWCL4cklkkhYvRRSC2OEX7YOZNmPQ==", + "requires": { + "vue-demi": "^0.13.11" + }, + "dependencies": { + "vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "requires": {} + } + } + }, + "@vuelidate/validators": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@vuelidate/validators/-/validators-2.0.0.tgz", + "integrity": "sha512-fQQcmDWfz7pyH5/JPi0Ng2GEgNK1pUHn/Z/j5rG/Q+HwhgIXvJblTPcZwKOj1ABL7V4UVuGKECvZCDHNGOwdrg==", + "requires": { + "vue-demi": "^0.13.11" + }, + "dependencies": { + "vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "requires": {} + } + } + }, "@vueuse/core": { "version": "9.7.0", "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.7.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index afcb4f6ee6..a795fbbfd1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,6 +25,8 @@ "@tiptap/extension-placeholder": "^2.0.0-beta.199", "@tiptap/starter-kit": "^2.0.0-beta.199", "@tiptap/vue-3": "^2.0.0-beta.199", + "@vuelidate/core": "^2.0.0", + "@vuelidate/validators": "^2.0.0", "@vueuse/core": "^9.7.0", "apollo-boost": "^0.4.9", "axios": "^0.22.0", diff --git a/frontend/src/assets/scss/form/select.scss b/frontend/src/assets/scss/form/select.scss index ae28831194..92f6a185dd 100644 --- a/frontend/src/assets/scss/form/select.scss +++ b/frontend/src/assets/scss/form/select.scss @@ -69,8 +69,7 @@ } // Selected icon for single selection -.el-select-dropdown - .el-select-dropdown__item.selected::after { +.el-select-dropdown .el-select-dropdown__item:not(.no-checkmark).selected::after { content: ''; position: absolute; top: 50%; @@ -117,7 +116,7 @@ } // Select dropdown item selected - &.selected { + &.selected, &.selected.hover { @apply font-medium bg-brand-50 text-gray-900; } diff --git a/frontend/src/premium/eagle-eye/components/eagle-eye-platforms-drawers.vue b/frontend/src/premium/eagle-eye/components/eagle-eye-platforms-drawers.vue new file mode 100644 index 0000000000..379a25d679 --- /dev/null +++ b/frontend/src/premium/eagle-eye/components/eagle-eye-platforms-drawers.vue @@ -0,0 +1,80 @@ + + + diff --git a/frontend/src/premium/eagle-eye/components/form/eagle-eye-settings-include.vue b/frontend/src/premium/eagle-eye/components/form/eagle-eye-settings-include.vue new file mode 100644 index 0000000000..b89ac5b8a4 --- /dev/null +++ b/frontend/src/premium/eagle-eye/components/form/eagle-eye-settings-include.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/frontend/src/premium/eagle-eye/components/list/eagle-eye-email-digest-drawer.vue b/frontend/src/premium/eagle-eye/components/list/eagle-eye-email-digest-drawer.vue index 7836f32a1b..ed539ebf87 100644 --- a/frontend/src/premium/eagle-eye/components/list/eagle-eye-email-digest-drawer.vue +++ b/frontend/src/premium/eagle-eye/components/list/eagle-eye-email-digest-drawer.vue @@ -20,103 +20,94 @@

- +
-
+
- - - - -
- + + + - + Daily + +

+ From Monday to Friday (results from + previous day) +

+
+ - -
- Daily -
-

- From Monday to Friday (results from - previous day) -

-
- +

-

- Weekly -
-

- Every Monday (results from previous - week) -

-
-
-
-
- + Every Monday (results from previous + week) +

+ + + +
-
-
+ Update email results based on your current @@ -222,7 +213,8 @@ type="primary" class="btn btn--md btn--primary" :loading="loadingUpdateSettings" - @click="doSubmit(formRef)" + :disabled="$v.$invalid || !hasFormChanged" + @click="doSubmit()" >Update
@@ -248,6 +240,10 @@ import { } from '@/shared/vuex/vuex.helpers' import Message from '@/shared/message/message' import platformOptions from '@/premium/eagle-eye/constants/eagle-eye-platforms.json' +import { email, required } from '@vuelidate/validators' +import useVuelidate from '@vuelidate/core' +import AppFormItem from '@/shared/form/form-item.vue' +import formChangeDetector from '@/shared/form/form-change' const props = defineProps({ modelValue: { @@ -263,36 +259,24 @@ const { loadingUpdateSettings } = mapState('eagleEye') const emit = defineEmits(['update:modelValue']) const rules = { - email: [ - { - required: true, - message: 'This field is required', - trigger: 'blur' - } - ], - frequency: [ - { - required: true, - message: 'This field is required', - trigger: 'blur' - } - ], - time: [ - { - required: true, - message: 'This field is required', - trigger: 'blur' - } - ] + email: { + required, + email + } } -const model = reactive({ +const form = reactive({ + active: false, + email: '', frequency: 'daily', time: '09:00', updateResults: true }) +const { hasFormChanged, formSnapshot } = + formChangeDetector(form) + +const $v = useVuelidate(rules, form) -const active = ref(false) const formRef = ref() const drawerModel = computed({ @@ -307,7 +291,7 @@ const drawerModel = computed({ const feed = ref(null) const results = computed(() => { - if (!model.updateResults) { + if (!form.updateResults) { if (currentUser.value && feed.value) { return feed.value } @@ -316,7 +300,7 @@ const results = computed(() => { }) const displayFeedWarning = computed(() => { - if (model.updateResults) { + if (form.updateResults) { return false } if ( @@ -348,40 +332,40 @@ const updateFeed = () => { const fillForm = (user) => { const { eagleEyeSettings } = user - active.value = eagleEyeSettings.emailDigestActive || false - model.email = + form.active = eagleEyeSettings.emailDigestActive || false + form.email = eagleEyeSettings.emailDigest?.email || user.email - model.frequency = + form.frequency = eagleEyeSettings.emailDigest?.frequency || 'daily' - model.time = eagleEyeSettings.emailDigest?.time || '09:00' - model.updateResults = !eagleEyeSettings.emailDigest + form.time = eagleEyeSettings.emailDigest?.time || '09:00' + form.updateResults = !eagleEyeSettings.emailDigest ? true : eagleEyeSettings.emailDigest?.matchFeedSettings feed.value = user.eagleEyeSettings.emailDigest.feed + + formSnapshot() } -const doSubmit = async (formEl) => { - if (!formEl) return - await formEl.validate((valid) => { - if (valid) { - const data = { - email: model.email, - frequency: model.frequency, - time: model.time, - matchFeedSettings: model.updateResults, - feed: !model.updateResults ? feed.value : undefined - } - doUpdateSettings({ - ...currentUser.value.eagleEyeSettings, - emailDigestActive: active.value, - emailDigest: data - }).then(() => { - Message.success( - 'Email Digest settings successfully updated' - ) - emit('update:modelValue', false) - }) +const doSubmit = async () => { + $v.value.$touch() + if (!$v.value.$invalid) { + const data = { + email: form.email, + frequency: form.frequency, + time: form.time, + matchFeedSettings: form.updateResults, + feed: !form.updateResults ? feed.value : undefined } - }) + doUpdateSettings({ + ...currentUser.value.eagleEyeSettings, + emailDigestActive: form.active, + emailDigest: data + }).then(() => { + Message.success( + 'Email Digest settings successfully updated' + ) + emit('update:modelValue', false) + }) + } } const handleCancel = () => { diff --git a/frontend/src/premium/eagle-eye/components/list/eagle-eye-settings-drawer.vue b/frontend/src/premium/eagle-eye/components/list/eagle-eye-settings-drawer.vue index 83db052560..a44b7dd21d 100644 --- a/frontend/src/premium/eagle-eye/components/list/eagle-eye-settings-drawer.vue +++ b/frontend/src/premium/eagle-eye/components/list/eagle-eye-settings-drawer.vue @@ -6,7 +6,7 @@ Keywords - +

*

-
-
- - - - + - - -
- Semantic match -
-

- Results semantically related -

-
- -
- Exact match -
-

- Results containing the keyword -

-
-
-
-
- - - -
+ + + +

- - + Date published -

+
@@ -156,9 +101,12 @@
+

+ For better results, we recommend choosing at + least 3 platforms. +

@@ -172,12 +120,11 @@ @click="emit('update:modelValue', false)" >Cancel - Update @@ -187,7 +134,6 @@ diff --git a/frontend/src/premium/eagle-eye/components/onboard/eagle-eye-platforms-step.vue b/frontend/src/premium/eagle-eye/components/onboard/eagle-eye-platforms-step.vue index b06138f996..e7e08b4fbb 100644 --- a/frontend/src/premium/eagle-eye/components/onboard/eagle-eye-platforms-step.vue +++ b/frontend/src/premium/eagle-eye/components/onboard/eagle-eye-platforms-step.vue @@ -20,12 +20,16 @@
-
+
Platforms *
+

+ For better results, we recommend choosing at least 3 + platforms. +

{ + a[b] = true + return a + }, {}) }) const headerContent = computed(() => { diff --git a/frontend/src/shared/form/form-change.js b/frontend/src/shared/form/form-change.js new file mode 100644 index 0000000000..b159178e9e --- /dev/null +++ b/frontend/src/shared/form/form-change.js @@ -0,0 +1,19 @@ +import { ref, computed } from 'vue' + +export default function formChangeDetector(form) { + const temporaryForm = ref('') + + function formSnapshot() { + temporaryForm.value = JSON.stringify(form) + } + + const hasFormChanged = computed(() => { + return temporaryForm.value !== JSON.stringify(form) + }) + + return { + temporaryForm, + formSnapshot, + hasFormChanged + } +} diff --git a/frontend/src/shared/form/form-item.vue b/frontend/src/shared/form/form-item.vue new file mode 100644 index 0000000000..ce6a022b26 --- /dev/null +++ b/frontend/src/shared/form/form-item.vue @@ -0,0 +1,84 @@ + + + + +