diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1619409f5..d31eae1ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,7 +68,7 @@ jobs: runs-on : ubuntu-latest strategy : matrix: - php: [7.3, 7.4, 8.0] + php: [7.4, 8.0] defaults : { run: { working-directory: ./server }} services : mysql: diff --git a/CHANGELOG.md b/CHANGELOG.md index b017c4b74..e37a716db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,11 @@ Ce projet adhère au principe du [Semantic Versioning](https://semver.org/spec/v ## 0.18.0 (UNRELEASED) +- __[CHANGEMENT CRITIQUE]__ Robert2 requiert maintenant au minimum PHP 7.4 pour fonctionner (#327). - Ajoute la possibilité de configurer les données affichées dans les événements du calendrier via la page des paramètres de l'application (fin du ticket #302). +- Il est maintenant possible de s'abonner depuis votre application de calendrier préférée (Google Agenda, Apple Calendrier, etc.) au calendrier Robert2 / Loxya. + Pour plus d'informations, rendez-vous dans les paramètres de votre instance Robert2 / Loxya, onglet "Calendrier" (#326). ## 0.17.1 (2022-01-06) diff --git a/client/package.json b/client/package.json index bdda07fa1..74a7ee64d 100644 --- a/client/package.json +++ b/client/package.json @@ -17,9 +17,7 @@ "deep-freeze-strict": "1.1.1", "invariant": "2.2.4", "js-cookie": "2.2.1", - "lodash.clonedeep": "4.5.0", - "lodash.pick": "4.4.0", - "lodash.times": "4.3.2", + "lodash": "^4.17.21", "moment": "2.29.1", "react": "17.0.2", "react-dom": "17.0.2", @@ -45,13 +43,12 @@ "@babel/core": "7.14.8", "@babel/preset-react": "7.16.7", "@babel/preset-typescript": "7.15.0", - "@pulsanova/eslint-config-vue": "2.1.2", "@pulsanova/eslint-config-react": "2.1.2", + "@pulsanova/eslint-config-vue": "2.1.2", "@pulsanova/stylelint-config-scss": "2.0.1", "@types/debounce": "1.2", "@types/invariant": "2.2", - "@types/lodash.clonedeep": "4.5", - "@types/lodash.times": "4.3", + "@types/lodash": "^4.14.178", "@types/react": "17.0", "@types/react-dom": "17.0", "@vue/babel-preset-app": "4.5.15", diff --git a/client/src/components/Alert/index.js b/client/src/components/Alert/index.js index 422d62986..72052d609 100644 --- a/client/src/components/Alert/index.js +++ b/client/src/components/Alert/index.js @@ -8,7 +8,7 @@ const ConfirmDelete = ($t, entityName, isSoft = true) => Swal.fire({ icon: 'warning', showCancelButton: true, customClass: { - confirmButton: isSoft ? 'swal2-confirm--trash' : 'swal2-confirm--delete', + confirmButton: isSoft ? 'swal2-confirm--warning' : 'swal2-confirm--danger', }, confirmButtonText: isSoft ? $t('yes-delete') : $t('yes-permanently-delete'), cancelButtonText: $t('cancel'), diff --git a/client/src/components/Button/_variables.scss b/client/src/components/Button/_variables.scss index 43478b11b..a285e1a9c 100644 --- a/client/src/components/Button/_variables.scss +++ b/client/src/components/Button/_variables.scss @@ -1,3 +1,58 @@ +@use '~@/style/globals'; +@use 'sass:color'; + /// Marge entre l'icône et le texte des boutons (quand il y en a une, d'icône) /// @type Number $icon-margin: 0.3571rem !default; + +// +// - Default variant +// + +$default-color: globals.$color-base-button !default; +$default-background: globals.$bg-color-button-default !default; + +$default-focused-color: color.adjust(globals.$bg-color-button-default, $lightness: 8%) !default; +$default-focused-background: globals.$color-hover-button !default; + +$default-active-color: color.adjust(globals.$bg-color-button-default, $lightness: -8%) !default; +$default-active-background: globals.$color-active-button !default; + +// +// - Success variant +// + +$success-variant-color: #fff !default; +$success-variant-background: globals.$bg-color-button-success !default; + +$success-variant-focused-color: $success-variant-color !default; +$success-variant-focused-background: color.adjust(globals.$bg-color-button-success, $lightness: 5%) !default; + +$success-variant-active-color: globals.$color-active-button !default; +$success-variant-active-background: color.adjust(globals.$bg-color-button-success, $lightness: -5%) !default; + +// +// - Warning variant +// + +$warning-variant-color: #fff !default; +$warning-variant-background: #be692d !default; + +$warning-variant-focused-color: $warning-variant-color !default; +$warning-variant-focused-background: color.adjust(#be692d, $lightness: 8%) !default; + +$warning-variant-active-color: globals.$color-active-button !default; +$warning-variant-active-background: color.adjust(#be692d, $lightness: 8%) !default; + +// +// - Danger variant +// + +$danger-variant-color: #fff !default; +$danger-variant-background: globals.$bg-color-button-danger !default; + +$danger-variant-focused-color: $danger-variant-color !default; +$danger-variant-focused-background: color.adjust(globals.$bg-color-button-danger, $lightness: 8%) !default; + +$danger-variant-active-color: globals.$color-active-button !default; +$danger-variant-active-background: color.adjust(globals.$bg-color-button-danger, $lightness: -8%) !default; diff --git a/client/src/components/Button/index.js b/client/src/components/Button/index.js index fd9cb064f..8206a724e 100644 --- a/client/src/components/Button/index.js +++ b/client/src/components/Button/index.js @@ -2,6 +2,8 @@ import './index.scss'; import { toRefs, computed } from '@vue/composition-api'; import Icon from '@/components/Icon'; +const BUTTON_TYPE = ['default', 'success', 'warning', 'danger']; + // type Props = { // /** // * Le type de bouton pour l'attribut `type` de la balise ` @@ -68,8 +76,15 @@ Button.props = { ['button', 'submit', 'reset'].includes(value) ), }, + type: { + type: String, + validator: (value) => BUTTON_TYPE.includes(value), + default: 'default', + }, icon: { type: String, default: undefined }, disabled: { type: Boolean, default: false }, }; +Button.emits = ['click']; + export default Button; diff --git a/client/src/components/Button/index.scss b/client/src/components/Button/index.scss index ad690e4a5..4fb452e58 100644 --- a/client/src/components/Button/index.scss +++ b/client/src/components/Button/index.scss @@ -1,7 +1,49 @@ @use './variables' as *; +@use '~@/style/globals'; +@use 'sass:color'; .Button { + display: inline-block; + padding: 0.55rem 0.78rem; + border: none; + border-radius: 2px; + background-color: $default-background; + color: $default-color; + font-size: 1rem; + line-height: 1.25; + text-decoration: none; + cursor: pointer; + transition: all 300ms; + + & + & { + margin-left: 5px; + } + + &:hover, + &:focus { + outline: 0; + background-color: $default-focused-color; + color: $default-focused-background; + } + + &:active { + background-color: $default-active-color; + color: $default-active-background; + } + + // + // - Disabled + // + + &--disabled { + cursor: not-allowed; + opacity: 0.6; + } + + // // - Icône + // + &::before, &__icon { display: inline-flex; @@ -18,4 +60,56 @@ &__icon + &__content { margin-left: $icon-margin; } + + // + // - Variantes + // + + &--success { + background-color: $success-variant-background; + color: $success-variant-color; + + &:hover, + &:focus { + background-color: $success-variant-focused-background; + color: $success-variant-focused-color; + } + + &:active { + background-color: $success-variant-active-background; + color: $success-variant-active-color; + } + } + + &--warning { + background-color: $warning-variant-background; + color: $warning-variant-color; + + &:hover, + &:focus { + background-color: $warning-variant-focused-background; + color: $warning-variant-focused-color; + } + + &:active { + background-color: $warning-variant-active-background; + color: $warning-variant-active-color; + } + } + + &--danger { + background-color: $danger-variant-background; + color: $danger-variant-color; + + &:hover, + &:focus { + background-color: $danger-variant-focused-background; + color: $danger-variant-focused-color; + } + + &:active { + background-color: $danger-variant-active-background; + color: $danger-variant-active-color; + } + } } diff --git a/client/src/components/EventDetails/Header/Actions/index.js b/client/src/components/EventDetails/Header/Actions/index.js index e5e703e9e..17ecb4a6c 100644 --- a/client/src/components/EventDetails/Header/Actions/index.js +++ b/client/src/components/EventDetails/Header/Actions/index.js @@ -90,7 +90,7 @@ const EventDetailsHeaderActions = (props, { root, emit }) => { title: __('please-confirm'), text: __('page-calendar.confirm-delete'), confirmButtonText: __('yes-delete'), - type: 'warning', + type: 'danger', }); if (!isConfirmed) { return; diff --git a/client/src/components/FormField/index.js b/client/src/components/FormField/index.js index 698c9717a..eb13c30f6 100644 --- a/client/src/components/FormField/index.js +++ b/client/src/components/FormField/index.js @@ -3,10 +3,13 @@ import moment from 'moment'; import { defineComponent } from '@vue/composition-api'; import Datepicker from '@/components/Datepicker'; import SwitchToggle from '@/components/SwitchToggle'; +import InputCopy from '@/components/InputCopy'; const ALLOWED_TYPES = [ 'text', 'email', + 'copy', + 'static', 'password', 'number', 'tel', @@ -84,6 +87,7 @@ export default defineComponent({ render() { const { $t: __, + $scopedSlots: slots, type, label, name, @@ -202,11 +206,18 @@ export default defineComponent({ /> )} + {type === 'copy' && ( + + )} + {type === 'static' && ( +

{value}

+ )} {errors && (
{errors[0]}
)} + {(!errors && slots.help) &&
{slots.help()}
} ); }, diff --git a/client/src/components/FormField/index.scss b/client/src/components/FormField/index.scss index e9762a978..c4a5f7108 100644 --- a/client/src/components/FormField/index.scss +++ b/client/src/components/FormField/index.scss @@ -23,10 +23,18 @@ &__input, &__textarea, - &__datepicker { + &__datepicker, + &__static-value { width: 90%; } + &__static-value { + margin: 0; + padding: 10px 0; + font-weight: 500; + font-style: italic; + } + &__switch { display: flex; } @@ -46,9 +54,13 @@ min-height: 50px; } + &__help, &__error { flex: 0 0 100%; margin-top: 0.3rem; + } + + &__error { color: globals.$text-danger-color; &__text { @@ -62,6 +74,10 @@ padding-right: 0; border-right: 0; border-radius: globals.$input-border-radius 0 0 globals.$input-border-radius; + + &:focus + #{$block}__addon { + border-color: globals.$input-focus-border-color; + } } } @@ -88,7 +104,8 @@ &__input, &__textarea, - &__datepicker { + &__datepicker, + &__static-value { flex: 1; } diff --git a/client/src/components/InputCopy/index.js b/client/src/components/InputCopy/index.js new file mode 100644 index 000000000..52c7328b5 --- /dev/null +++ b/client/src/components/InputCopy/index.js @@ -0,0 +1,84 @@ +import './index.scss'; +import Button from '@/components/Button'; + +// @vue/component +export default { + name: 'InputCopy', + props: { + value: { + type: [String, Number], + required: true, + }, + }, + data() { + return { + isCopied: false, + }; + }, + methods: { + // ------------------------------------------------------ + // - + // - Handlers + // - + // ------------------------------------------------------ + + handleFocus() { + this.selectInputContent(); + }, + + handleCopy() { + const cancelSelection = this.selectInputContent(); + if (!document.execCommand('copy')) { + return; + } + cancelSelection(); + + // - Affiche l'état "Copié !" et le revert au bout de 2 secondes. + setTimeout(() => { this.isCopied = false; }, 2000); + this.isCopied = true; + }, + + // ------------------------------------------------------ + // - + // - Internal methods + // - + // ------------------------------------------------------ + + selectInputContent() { + const range = document.createRange(); + range.selectNode(this.$refs.input); + + window.getSelection().removeAllRanges(); + window.getSelection().addRange(range); + + return () => { window.getSelection().removeAllRanges(); }; + }, + }, + render() { + const { + $t: __, + value, + isCopied, + handleCopy, + handleFocus, + } = this; + + return ( +
+ +
+ +
+
+ ); + }, +}; diff --git a/client/src/components/InputCopy/index.scss b/client/src/components/InputCopy/index.scss new file mode 100644 index 000000000..1d391047c --- /dev/null +++ b/client/src/components/InputCopy/index.scss @@ -0,0 +1,41 @@ +@use '~@/style/globals'; +@use 'sass:math'; + +.InputCopy { + $block: &; + + display: flex; + flex-wrap: nowrap; + + &__input { + flex: 1; + padding-right: math.div(globals.$input-padding-horizontal, 2); + border-right: 0; + border-radius: globals.$input-border-radius 0 0 globals.$input-border-radius; + + &:focus + #{$block}__button-wrapper { + border-color: globals.$input-focus-border-color; + } + } + + &__button-wrapper { + display: inline-block; + min-width: 2.7rem; + border: globals.$input-border-size solid globals.$bg-color-input-normal; + border-left: 0; + border-radius: 0 globals.$input-border-radius globals.$input-border-radius 0; + background-color: globals.$bg-color-input-normal; + color: globals.$color-input; + + // stylelint-disable-next-line order/properties-order + padding: + calc(#{globals.$input-padding-vertical} - 0.3rem) + calc(#{globals.$input-padding-horizontal} - 0.3rem) + calc(#{globals.$input-padding-vertical} - 0.3rem) + math.div(globals.$input-padding-horizontal, 2); + + .Button { + padding: 0.4rem 0.7rem; + } + } +} diff --git a/client/src/locale/en/common.js b/client/src/locale/en/common.js index aa59a481e..b3b7c3ba4 100644 --- a/client/src/locale/en/common.js +++ b/client/src/locale/en/common.js @@ -20,20 +20,25 @@ export default { 'yes': "Yes", 'no': "No", + 'warning': "Warning!", 'loading': "Loading...", 'please-confirm': "Please confirm...", 'yes-delete': "Yes, move in trash bin", 'yes-permanently-delete': "Yes, permanently delete", + 'yes-regenerate-link': "Yes, regenerate the link", 'yes-restore': "Oui, restaurer", 'cancel': "Cancel", 'close': "Close", 'copy-to-clipboard': "Copy to clipboard", 'copied-in-clipboard': "Copied in clipboard!", + 'copy': "Copy", + 'copied': "Copied!", 'almost-done': "Almost done...", 'done': "Done", 'refresh-page': "Refresh the page", 'take-control': "Take control", 'update-in-progress': "Update in progress", + 'regenerate-link': "Regenerate link", 'please-choose': "Please choose...", 'start-typing-to-search': "Start typing to search...", diff --git a/client/src/locale/en/pages.js b/client/src/locale/en/pages.js index 0089cc7ff..4ccee8f77 100644 --- a/client/src/locale/en/pages.js +++ b/client/src/locale/en/pages.js @@ -397,6 +397,21 @@ export default { 'events-display-section-title': "Data that will be displayed in the calendar events", 'showLocation': "Show the location of the event?", 'showBorrower': "Show the beneficiary / borrower?", + 'public-calendar-section-title': "Calendar external subscription", + 'enable-public-calendar': "Enable external calendar access?", + 'public-calendar-url': "External calendar URL", + 'save-to-get-calendar-url': "Please save your changes to get the calendar URL.", + 'public-calendar-help': ( + `This allows you to publish the main calendar. Any person in possession of this link will be able to consult the events of your calendar, without being connected to the application! So be sure to only share this link with people you trust.\n + To use this link, go to your compatible calendar application, and look for the "New Calendar Subscription" feature. Please note that the refresh rate in these applications is highly variable, so they may display event changes with a delay.` + ), + 'public-calendar-url-reset-help': "If you suspect that the calendar link has been shared with unwanted third parties, you can re-generate the link by clicking here:", + 'public-calendar-url-reset-warning': ( + "If you regenerate the link, the previous one will be revoked and you will need to communicate this new link again to people who legitimately have access to the calendar since theirs will be revoked.\n\n" + + "Do you really want to proceed?" + ), + 'public-calendar-url-reset-error': "An error occurred while regenerating the public calendar link, please try again.", + 'public-calendar-url-reset-success': "The public calendar link has been successfully re-generated! The new one is available above.", }, }, diff --git a/client/src/locale/fr/common.js b/client/src/locale/fr/common.js index 1a425c294..45f6bf48f 100644 --- a/client/src/locale/fr/common.js +++ b/client/src/locale/fr/common.js @@ -20,20 +20,25 @@ export default { 'yes': "Oui", 'no': "Non", + 'warning': "Attention\u00a0!", 'loading': "Chargement en cours...", 'please-confirm': "Veuillez confirmer...", 'yes-delete': "Oui, mettre à la corbeille", 'yes-permanently-delete': "Oui, supprimer définitivement", + 'yes-regenerate-link': "Oui, re-générer le lien", 'yes-restore': "Oui, restaurer", 'cancel': "Annuler", 'close': "Fermer", 'copy-to-clipboard': "Copier dans le presse-papier", 'copied-in-clipboard': "Copié dans le presse-papier\u00a0!", + 'copy': "Copier", + 'copied': "Copié\u00a0!", 'almost-done': "Presque terminé...", 'done': "Terminé", 'refresh-page': "Actualiser la page", 'take-control': "Prendre la main", 'update-in-progress': "Modification en cours", + 'regenerate-link': "Re-générer le lien", 'please-choose': "Veuillez choisir...", 'start-typing-to-search': "Commencez à écrire pour rechercher...", diff --git a/client/src/locale/fr/pages.js b/client/src/locale/fr/pages.js index 4410e9f74..19961b8d2 100644 --- a/client/src/locale/fr/pages.js +++ b/client/src/locale/fr/pages.js @@ -397,6 +397,21 @@ export default { 'events-display-section-title': "Données affichées dans les événements du calendrier", 'showLocation': "Afficher le lieu de l'événement\u00a0?", 'showBorrower': "Afficher le bénéficiaire / emprunteur\u00a0?", + 'public-calendar-section-title': "Abonnement externe au calendrier", + 'enable-public-calendar': "Activer l'accès externe au calendrier\u00a0?", + 'public-calendar-url': "URL du calendrier externe", + 'save-to-get-calendar-url': "Veuillez sauvegarder vos modifications pour obtenir l'URL.", + 'public-calendar-help': ( + `Ceci vous permet de publier le calendrier principal. Toute personne en possession de ce lien pourra donc consulter les événements de votre calendrier, sans être connecté à l'application\u00a0! Veillez donc bien à ne communiquer ce lien qu'à des personnes de confiance.\n + Pour utiliser ce lien, rendez-vous dans votre application de calendrier compatible, et cherchez la fonctionnalité "Abonnement à un calendrier". Veuillez noter que le taux de rafraîchissement des données dans ces applications est très variable, elles peuvent donc afficher les modifications des événements avec du retard.` + ), + 'public-calendar-url-reset-help': "Si vous soupçonnez que le lien du calendrier a été communiqué à des tiers non désirés, vous pouvez re-générer le lien en cliquant ici\u00a0:", + 'public-calendar-url-reset-warning': ( + "Si vous re-générez le lien, le précédent sera révoqué et vous devrez communiquer ce nouveau lien aux personnes qui ont légitimement accès au calendrier pour qu'elles le mette à jour.\n\n" + + "Voulez-vous vraiment continuer\u00a0?" + ), + 'public-calendar-url-reset-error': "Une erreur est survenue lors de la re-génération du lien du calendrier public, veuillez re-essayer.", + 'public-calendar-url-reset-success': "Le lien du calendrier public a bien été re-généré\u00a0! Le nouveau lien est disponible ci-dessus.", }, }, diff --git a/client/src/pages/Categories/index.js b/client/src/pages/Categories/index.js index 18ed0af83..97537489d 100644 --- a/client/src/pages/Categories/index.js +++ b/client/src/pages/Categories/index.js @@ -96,7 +96,7 @@ export default { title: this.$t('please-confirm'), text: this.$t(`page-${entity}.confirm-permanently-delete`), confirmButtonText: this.$t(`yes-permanently-delete`), - type: 'delete', + type: 'danger', }); if (!isConfirmed) { diff --git a/client/src/pages/Event/Step1/index.js b/client/src/pages/Event/Step1/index.js index 3cad04ed3..8b960d8ea 100644 --- a/client/src/pages/Event/Step1/index.js +++ b/client/src/pages/Event/Step1/index.js @@ -1,6 +1,6 @@ import './index.scss'; import moment from 'moment'; -import pick from 'lodash.pick'; +import pick from 'lodash/pick'; import Config from '@/globals/config'; import { DATE_DB_FORMAT } from '@/globals/constants'; import FormField from '@/components/FormField'; diff --git a/client/src/pages/Event/Step3/Modal/index.js b/client/src/pages/Event/Step3/Modal/index.js index 822f2e04b..be575b67b 100644 --- a/client/src/pages/Event/Step3/Modal/index.js +++ b/client/src/pages/Event/Step3/Modal/index.js @@ -190,7 +190,7 @@ export default { const { value: isConfirmed } = await confirm({ text: this.$t('page-events.technician-item.confirm-permanently-delete'), confirmButtonText: this.$t('yes-permanently-delete'), - type: 'delete', + type: 'danger', }); if (!isConfirmed) { return; diff --git a/client/src/pages/Event/Step3/index.js b/client/src/pages/Event/Step3/index.js index 841101ac1..f589de149 100644 --- a/client/src/pages/Event/Step3/index.js +++ b/client/src/pages/Event/Step3/index.js @@ -230,7 +230,7 @@ export default { const { value: isConfirmed } = await confirm({ text: this.$t('page-events.technician-item.confirm-permanently-delete'), confirmButtonText: this.$t('yes-permanently-delete'), - type: 'delete', + type: 'danger', }); if (!isConfirmed) { callback(null); diff --git a/client/src/pages/Materials/index.js b/client/src/pages/Materials/index.js index 342ad6b17..1918a6bb1 100644 --- a/client/src/pages/Materials/index.js +++ b/client/src/pages/Materials/index.js @@ -210,7 +210,7 @@ export default { const confirmButtonText = isSoft ? this.$t('yes-delete') : this.$t('yes-permanently-delete'); - const type = isSoft ? 'trash' : 'delete'; + const type = isSoft ? 'warning' : 'danger'; const { value: isConfirmed } = await confirm({ text, confirmButtonText, type }); if (!isConfirmed) { return; diff --git a/client/src/pages/Settings/Calendar/index.js b/client/src/pages/Settings/Calendar/index.js index e5cc85477..a7cf6a574 100644 --- a/client/src/pages/Settings/Calendar/index.js +++ b/client/src/pages/Settings/Calendar/index.js @@ -1,7 +1,9 @@ import './index.scss'; import { ref, computed, reactive } from '@vue/composition-api'; import axios from 'axios'; -import cloneDeep from 'lodash.clonedeep'; +import pick from 'lodash/pick'; +import cloneDeep from 'lodash/cloneDeep'; +import { confirm } from '@/utils/alert'; import apiSettings from '@/stores/api/settings'; import useI18n from '@/hooks/vue/useI18n'; import Help from '@/components/Help'; @@ -14,7 +16,18 @@ const CalendarSettings = (props, { root }) => { const isSaving = ref(false); const isSaved = ref(false); const error = ref(null); - const values = reactive(cloneDeep(root.$store.state.settings.calendar)); + + // - State "temporaire" juste pendant la durée de vie de ce formulaire. + // (on veut que le bouton de "Régénération du lien" du calendrier public + // ré-apparaisse lorsqu'on reviendra sur le formulaire) + const hasRegeneratedCalendarLink = ref(false); + + const persistedData = computed(() => root.$store.state.settings.calendar); + const values = reactive(pick(cloneDeep(persistedData.value), [ + 'event.showLocation', + 'event.showBorrower', + 'public.enabled', + ])); const help = computed(() => ( isSaved.value @@ -50,40 +63,126 @@ const CalendarSettings = (props, { root }) => { } }; - return () => ( -
- -
-
-

{__('page-settings.calendar.events-display-section-title')}

- + const handleRegenerateCalendarUrl = async () => { + const { value: isConfirmed } = await confirm({ + title: __('warning'), + text: __('page-settings.calendar.public-calendar-url-reset-warning'), + confirmButtonText: __('yes-regenerate-link'), + type: 'warning', + }); + if (!isConfirmed) { + return; + } + + try { + await apiSettings.reset('calendar.public.url'); + root.$store.dispatch('settings/fetch'); + hasRegeneratedCalendarLink.value = true; + } catch { + root.$toasted.error(__('page-settings.calendar.public-calendar-url-reset-error')); + } + }; + + return () => { + const renderPublicCalendarUrl = () => { + const isClientSideEnabled = values.public.enabled; + if (!isClientSideEnabled) { + return null; + } + + const isServerSideEnabled = persistedData.value.public.enabled; + if (!isServerSideEnabled) { + return ( -
-
- -
-
-
- ); + ); + } + + return ( + { + if (hasRegeneratedCalendarLink.value) { + return ( + + {__('page-settings.calendar.public-calendar-url-reset-success')} + + ); + } + + return ( + + {__('page-settings.calendar.public-calendar-url-reset-help')} + + + ); + }, + }} + /> + ); + }; + + return ( +
+ +
+
+

{__('page-settings.calendar.events-display-section-title')}

+ + +
+
+

{__('page-settings.calendar.public-calendar-section-title')}

+

{__('page-settings.calendar.public-calendar-help')}

+ + {renderPublicCalendarUrl()} +
+
+ +
+
+
+ ); + }; }; export default CalendarSettings; diff --git a/client/src/pages/Settings/Calendar/index.scss b/client/src/pages/Settings/Calendar/index.scss index 9b9026ef3..d43ae10b0 100644 --- a/client/src/pages/Settings/Calendar/index.scss +++ b/client/src/pages/Settings/Calendar/index.scss @@ -1,29 +1,51 @@ @use '~@/style/globals'; .CalendarSettings { - &__form { - max-width: globals.$form-max-width; - } - &__section { margin-top: globals.$content-padding-large-vertical; .FormField { - &__label { - flex-basis: 250px; + + .FormField { + margin-top: globals.$content-padding-small-vertical; } - &__error__text { - padding-left: 250px; - } + @media screen and (min-width: globals.$screen-tablet) { + $label-width: 250px; - + .FormField { - margin-top: globals.$content-padding-small-vertical; + &__label { + flex-basis: $label-width; + } + + &__help, + &__error__text { + padding-left: $label-width; + } } } } + &__help { + margin-bottom: 20px; + white-space: pre-line; + } + &__actions { margin-top: globals.$content-padding-large-vertical; + text-align: center; + } + + &__public-calendar-url { + &__help { + line-height: 1.65; + + .Button { + margin-left: 0.45rem; + padding: 0.35rem 0.6rem; + } + + &--success { + color: globals.$text-success-color; + } + } } } diff --git a/client/src/pages/Settings/EventSummary/index.js b/client/src/pages/Settings/EventSummary/index.js index 2b04865e4..7709d728e 100644 --- a/client/src/pages/Settings/EventSummary/index.js +++ b/client/src/pages/Settings/EventSummary/index.js @@ -1,7 +1,7 @@ import './index.scss'; import { ref, computed, reactive, onMounted } from '@vue/composition-api'; import axios from 'axios'; -import cloneDeep from 'lodash.clonedeep'; +import cloneDeep from 'lodash/cloneDeep'; import useI18n from '@/hooks/vue/useI18n'; import apiSettings from '@/stores/api/settings'; import Help from '@/components/Help'; @@ -124,7 +124,7 @@ const EventSummarySettings = (props, { root }) => {