diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/hosting.module.js b/packages/manager/apps/web/client/app/hosting/dashboard/hosting.module.js index 439dbf6ffe01..79b0c65563e2 100644 --- a/packages/manager/apps/web/client/app/hosting/dashboard/hosting.module.js +++ b/packages/manager/apps/web/client/app/hosting/dashboard/hosting.module.js @@ -21,6 +21,7 @@ import task from '../task/task.module'; import userLogs from '../user-logs/user-logs.module'; import hostingDomainOffersComponent from '../../components/hosting-domain-offers'; import hostingAbuseUnblock from './abuse-unblock'; +import hostingRenewPeriod from './renew-period'; const moduleName = 'ovhManagerHosting'; @@ -48,6 +49,7 @@ angular userLogs, hostingDomainOffersComponent, hostingAbuseUnblock, + hostingRenewPeriod, ]) .config(routing) .run(/* @ngTranslationsInject:json ./translations */); diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/index.js b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/index.js new file mode 100644 index 000000000000..baa6580c9b68 --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/index.js @@ -0,0 +1,25 @@ +import angular from 'angular'; +import '@uirouter/angularjs'; +import 'oclazyload'; + +const moduleName = 'ovhManagerHostingRenewPeriodLazyLoading'; + +angular + .module(moduleName, ['ui.router', 'oc.lazyLoad']) + .config( + /* @ngInject */ ($stateProvider) => { + $stateProvider.state('app.hosting.dashboard.renew-period.**', { + url: '/renew-period', + lazyLoad: ($transition$) => { + const $ocLazyLoad = $transition$.injector().get('$ocLazyLoad'); + + return import('./renew-period.module').then((mod) => + $ocLazyLoad.inject(mod.default || mod), + ); + }, + }); + }, + ) + .run(/* @ngTranslationsInject:json ./translations */); + +export default moduleName; diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.component.js b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.component.js new file mode 100644 index 000000000000..7236d576ec2f --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.component.js @@ -0,0 +1,12 @@ +import template from './renew-period.html'; +import controller from './renew-period.controller'; + +export default { + bindings: { + goBack: '<', + serviceId: '<', + serviceName: '<', + }, + template, + controller, +}; diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.constant.js b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.constant.js new file mode 100644 index 000000000000..38776db50c52 --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.constant.js @@ -0,0 +1,40 @@ +export const BILLING_RENEW_URLS = { + CA: + 'https://ca.ovh.com/fr/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + CZ: 'https://www.ovh.cz/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + DE: 'https://www.ovh.de/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + EN: + 'https://www.ovh.co.uk/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + ES: 'https://www.ovh.es/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + FI: + 'https://www.ovh-hosting.fi/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + FR: + 'https://eu.ovh.com/fr/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + GB: + 'https://www.ovh.co.uk/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + IE: 'https://www.ovh.ie/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + IT: 'https://www.ovh.it/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + LT: 'https://www.ovh.lt/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + MA: + 'https://www.ovh.com/ma/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + NL: 'https://www.ovh.nl/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + PL: 'https://www.ovh.pl/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + PT: 'https://www.ovh.pt/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + QC: + 'https://ca.ovh.com/fr/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + RU: + 'https://www.ovh.co.uk/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + SN: 'https://www.ovh.sn/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + TN: + 'https://www.ovh.com/tn/cgi-bin/order/renew.cgi?domainChooser={serviceName}', + WE: + 'https://ca.ovh.com/fr/cgi-bin/order/renew.cgi?domainChooser={serviceName}', +}; + +// Fallback subsidiary used when the customer's subsidiary has no dedicated URL. +export const DEFAULT_BILLING_RENEW_SUBSIDIARY = 'FR'; + +export default { + BILLING_RENEW_URLS, + DEFAULT_BILLING_RENEW_SUBSIDIARY, +}; diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.controller.js b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.controller.js new file mode 100644 index 000000000000..82d4e8c71a49 --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.controller.js @@ -0,0 +1,169 @@ +import { + convertPeriodToMonths, + getBillingRenewUrl, +} from './renew-period.service'; + +export default class HostingRenewPeriodController { + /* @ngInject */ + constructor($translate, $window, coreConfig, HostingRenewPeriodService) { + this.$translate = $translate; + this.$window = $window; + this.coreConfig = coreConfig; + this.HostingRenewPeriodService = HostingRenewPeriodService; + } + + $onInit() { + this.isLoading = true; + this.isSubmitting = false; + this.loadError = null; + + // Service capabilities & current state + this.isUpfront = false; + this.manualModeAvailable = false; + this.currentPeriod = null; + this.isCurrentlyManual = false; + + // Available renewal frequencies + this.availableMonths = []; + this.optionsByMonths = {}; + + this.selectedMonths = undefined; + this.isManualSelected = false; + + if (!this.serviceId) { + this.loadError = true; + this.isLoading = false; + return; + } + + this.HostingRenewPeriodService.getServiceDetail(this.serviceId) + .then((detail) => this.applyServiceDetail(detail)) + .then(() => + this.HostingRenewPeriodService.getAvailableEngagements(this.serviceId), + ) + .then((options) => { + const opts = options || []; + this.availableMonths = [...new Set(opts.map((o) => o.months))].sort( + (a, b) => a - b, + ); + this.optionsByMonths = {}; + opts.forEach((o) => { + if (!this.optionsByMonths[o.months]) { + this.optionsByMonths[o.months] = o; + } + }); + }) + .catch(() => { + this.loadError = true; + }) + .finally(() => { + this.isLoading = false; + }); + } + + applyServiceDetail(detail) { + const engagementConfig = detail?.billing?.pricing?.engagementConfiguration; + this.isUpfront = engagementConfig?.type === 'upfront'; + + this.servicePricingMode = detail?.billing?.pricing?.pricingMode; + + const modes = detail?.billing?.renew?.capacities?.mode || []; + this.manualModeAvailable = modes.includes('manual'); + + const currentMode = detail?.billing?.renew?.current?.mode; + const currentPeriodStr = detail?.billing?.renew?.current?.period; + + this.currentPeriod = currentPeriodStr + ? convertPeriodToMonths(currentPeriodStr) + : null; + this.isCurrentlyManual = currentMode === 'manual' || !currentPeriodStr; + } + + // ── UI helpers ─────────────────────────────────────────────── + + periodLabel(months) { + if (!months) return ''; + + if (months % 12 === 0) { + const years = months / 12; + return this.$translate.instant( + years > 1 + ? 'hosting_renew_period_periodYear_plural' + : 'hosting_renew_period_periodYear', + { count: years }, + ); + } + return this.$translate.instant( + months > 1 + ? 'hosting_renew_period_periodMonth_plural' + : 'hosting_renew_period_periodMonth', + { count: months }, + ); + } + + selectMonths(months) { + this.selectedMonths = months; + this.isManualSelected = false; + } + + selectManual() { + this.isManualSelected = true; + this.selectedMonths = undefined; + } + + isMonthsSelected(months) { + return !this.isManualSelected && this.selectedMonths === months; + } + + hasSelection() { + return this.selectedMonths !== undefined || this.isManualSelected; + } + + cancel() { + return this.goBack(); + } + + openBillingRenew() { + const subsidiary = this.coreConfig.getUser()?.ovhSubsidiary; + const url = getBillingRenewUrl(subsidiary, this.serviceName); + if (url) { + this.$window.open(url, '_blank', 'noopener,noreferrer'); + } + } + + submit() { + if (!this.hasSelection() || this.isSubmitting) return null; + this.isSubmitting = true; + + const pricingMode = this.isManualSelected + ? this.servicePricingMode + : this.optionsByMonths[this.selectedMonths]?.pricingMode; + + const submitPromise = this.HostingRenewPeriodService.submitEngagementRequest( + this.serviceId, + pricingMode, + ).then((response) => { + if (response && response.status === 200) { + this.openBillingRenew(); + } + return response; + }); + + return submitPromise + .then(() => + this.goBack( + this.$translate.instant('hosting_renew_period_successToast'), + 'success', + true, + ), + ) + .catch(({ data } = {}) => + this.goBack( + this.$translate.instant('hosting_renew_period_errorToast', { + error: data?.message || '', + }), + 'danger', + ), + ); + } +} diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.html b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.html new file mode 100644 index 000000000000..6bd13f689c19 --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.html @@ -0,0 +1,111 @@ + +
+

+ +
+ +
+ +
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
+ + + +
+ + +
+
+
+
diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.module.js b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.module.js new file mode 100644 index 000000000000..acce880e6850 --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.module.js @@ -0,0 +1,23 @@ +import angular from 'angular'; +import '@ovh-ux/ui-kit'; +import uiRouter from '@uirouter/angularjs'; + +import component from './renew-period.component'; +import routing from './renew-period.routing'; +import service from './renew-period.service'; + +const moduleName = 'ovhManagerHostingRenewPeriod'; + +angular + .module(moduleName, [ + 'ngTranslateAsyncLoader', + 'oui', + 'pascalprecht.translate', + uiRouter, + ]) + .component('hostingRenewPeriodComponent', component) + .service('HostingRenewPeriodService', service) + .config(routing) + .run(/* @ngTranslationsInject:json ./translations */); + +export default moduleName; diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.routing.js b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.routing.js new file mode 100644 index 000000000000..56160aa90ac4 --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.routing.js @@ -0,0 +1,19 @@ +export default /* @ngInject */ ($stateProvider) => { + $stateProvider.state('app.hosting.dashboard.renew-period', { + url: '/renew-period', + layout: { name: 'modal', keyboard: true }, + views: { + modal: { + component: 'hostingRenewPeriodComponent', + }, + }, + resolve: { + breadcrumb: /* @ngInject */ ($translate) => + $translate.instant('hosting_renew_period_title'), + serviceName: /* @ngInject */ ($transition$) => + $transition$.params().productId, + serviceId: /* @ngInject */ (HostingRenewPeriodService, serviceName) => + HostingRenewPeriodService.getServiceId(serviceName), + }, + }); +}; diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.service.js b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.service.js new file mode 100644 index 000000000000..9389ae65ff59 --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/renew-period.service.js @@ -0,0 +1,116 @@ +import { + BILLING_RENEW_URLS, + DEFAULT_BILLING_RENEW_SUBSIDIARY, +} from './renew-period.constant'; + +/** + * Get the subsidiary-specific renew order URL with the service name injected + * @param {string} subsidiary + * @param {string} serviceName + * @returns {string|null} + */ +export function getBillingRenewUrl(subsidiary, serviceName) { + const url = + BILLING_RENEW_URLS[subsidiary] || + BILLING_RENEW_URLS[DEFAULT_BILLING_RENEW_SUBSIDIARY]; + if (!url) return null; + return url.replace('{serviceName}', serviceName || ''); +} + +/** + * Convert an ISO 8601 period (e.g. P1Y, P3M) to a number of months + * @param {string} period + * @returns {number} + */ +export function convertPeriodToMonths(period) { + if (!period || typeof period !== 'string') return 0; + const value = parseInt(period.slice(1, -1), 10); + const unit = period.slice(-1); + if (!Number.isFinite(value)) return 0; + return unit === 'Y' ? value * 12 : value; +} + +export default class HostingRenewPeriodService { + /* @ngInject */ + constructor($http, $q) { + this.$http = $http; + this.$q = $q; + } + + /** + * Get the numeric service id from the hosting service name + * @param {string} serviceName + * @returns {Promise} + */ + getServiceId(serviceName) { + return this.$http + .get('/services', { params: { resourceName: serviceName } }) + .then(({ data }) => + Array.isArray(data) && data.length ? data[0] : null, + ); + } + + /** + * Get service details (capabilities and current renewal state) + * @param {number} serviceId + * @returns {Promise} + */ + getServiceDetail(serviceId) { + return this.$http.get(`/services/${serviceId}`).then(({ data }) => data); + } + + /** + * Get available renewal frequencies, deduplicated by months and sorted ascending + * @param {number} serviceId + * @returns {Promise} + */ + getAvailableEngagements(serviceId) { + return this.$http + .get(`/services/${serviceId}/billing/engagement/available`) + .then(({ data }) => { + const byMonths = new Map(); + (data || []).forEach((offer) => { + if (!offer?.duration) return; + const months = convertPeriodToMonths(offer.duration); + if (!(months > 0)) return; + if (!byMonths.has(months)) { + byMonths.set(months, { + months, + duration: offer.duration, + pricingMode: offer.pricingMode, + description: offer.description, + price: offer.price, + raw: offer, + }); + } + }); + return [...byMonths.values()].sort((a, b) => a.months - b.months); + }); + } + + /** + * Submit an engagement request: delete the pending one if any, then post the chosen pricingMode + * @param {number} serviceId + * @param {string} pricingMode + * @returns {Promise} + */ + submitEngagementRequest(serviceId, pricingMode) { + if (!pricingMode) { + return this.$q.reject( + new Error('submitEngagementRequest: missing pricingMode'), + ); + } + const path = `/services/${serviceId}/billing/engagement/request`; + + return this.$http + .get(path) + .then( + () => this.$http.delete(path), + (err) => { + if (err?.status === 404) return null; + return this.$q.reject(err); + }, + ) + .then(() => this.$http.post(path, { pricingMode })); + } +} diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_de_DE.json b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_de_DE.json new file mode 100644 index 000000000000..8a8fad984a5e --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_de_DE.json @@ -0,0 +1,18 @@ +{ + "hosting_renew_period_title": "Die Verwaltung der Erneuerung", + "hosting_renew_period_description": "Wählen Sie die gewünschte Häufigkeit der automatischen Erneuerung aus.", + "hosting_renew_period_periodMonth": "{{count}} Monate", + "hosting_renew_period_periodMonth_plural": "{{count}} Monate", + "hosting_renew_period_periodYear": "{{count}} Jahr", + "hosting_renew_period_periodYear_plural": "{{count}} Jahre", + "hosting_renew_period_currentPeriod": "Aktuelle Periode: {{period}}", + "hosting_renew_period_noPeriodsAvailable": "Für diesen Dienst sind keine Erneuerungszeiträume verfügbar.", + "hosting_renew_period_loadError": "Die verfügbaren Erneuerungszeiträume konnten nicht geladen werden.", + "hosting_renew_period_noPeriod": "Keine Häufigkeit — manuelle Erneuerung", + "hosting_renew_period_noPeriodHint": "Achtung, Ihr Dienst wird nicht automatisch erneuert. Sie müssen ihn manuell vor Ablauf erneuern.", + "hosting_renew_period_or": "oder", + "hosting_renew_period_submit": "Bestätigen", + "hosting_renew_period_cancel": "Abbrechen", + "hosting_renew_period_successToast": "Die Erneuerungsperiode wurde erfolgreich aktualisiert.", + "hosting_renew_period_errorToast": "Es ist ein Fehler aufgetreten: {{error}}." +} diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_en_GB.json b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_en_GB.json new file mode 100644 index 000000000000..2fe572bf2c7d --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_en_GB.json @@ -0,0 +1,18 @@ +{ + "hosting_renew_period_title": "Manage the renewal", + "hosting_renew_period_description": "Select the desired automatic renewal frequency.", + "hosting_renew_period_periodMonth": "{{count}} months", + "hosting_renew_period_periodMonth_plural": "{{count}} months", + "hosting_renew_period_periodYear": "{{count}} year", + "hosting_renew_period_periodYear_plural": "{{count}} years", + "hosting_renew_period_currentPeriod": "Current period: {{period}}", + "hosting_renew_period_noPeriodsAvailable": "No renewal period is available for this service.", + "hosting_renew_period_loadError": "Unable to load the available renewal periods.", + "hosting_renew_period_noPeriod": "No frequency — manual renewal", + "hosting_renew_period_noPeriodHint": "Warning, your service will not be renewed automatically. You will need to renew it manually before it expires.", + "hosting_renew_period_or": "or", + "hosting_renew_period_submit": "Confirm", + "hosting_renew_period_cancel": "Cancel", + "hosting_renew_period_successToast": "The renewal period has been successfully updated.", + "hosting_renew_period_errorToast": "An error has occurred: {{error}}." +} diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_es_ES.json b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_es_ES.json new file mode 100644 index 000000000000..8800cae2cd47 --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_es_ES.json @@ -0,0 +1,18 @@ +{ + "hosting_renew_period_title": "Gestionar la renovación", + "hosting_renew_period_description": "Seleccione la frecuencia de renovación automática deseada.", + "hosting_renew_period_periodMonth": "{{count}} meses", + "hosting_renew_period_periodMonth_plural": "{{count}} meses", + "hosting_renew_period_periodYear": "{{count}} año", + "hosting_renew_period_periodYear_plural": "{{count}} años", + "hosting_renew_period_currentPeriod": "Período actual: {{period}}", + "hosting_renew_period_noPeriodsAvailable": "No hay ningún período de renovación disponible para este servicio.", + "hosting_renew_period_loadError": "No se pueden cargar los períodos de renovación disponibles.", + "hosting_renew_period_noPeriod": "Ninguna frecuencia — renovación manual", + "hosting_renew_period_noPeriodHint": "Atención, su servicio no se renovará automáticamente. Deberá renovarlо manualmente antes de su expiración.", + "hosting_renew_period_or": "o", + "hosting_renew_period_submit": "Aceptar", + "hosting_renew_period_cancel": "Cancelar", + "hosting_renew_period_successToast": "El período de renovación se ha actualizado con éxito.", + "hosting_renew_period_errorToast": "Se ha producido un error: {{error}}." +} diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_fr_CA.json b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_fr_CA.json new file mode 100644 index 000000000000..4bde7c3ad934 --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_fr_CA.json @@ -0,0 +1,18 @@ +{ + "hosting_renew_period_title": "Gérer le renouvellement", + "hosting_renew_period_description": "Sélectionnez la fréquence de renouvellement automatique souhaitée.", + "hosting_renew_period_periodMonth": "{{count}} mois", + "hosting_renew_period_periodMonth_plural": "{{count}} mois", + "hosting_renew_period_periodYear": "{{count}} an", + "hosting_renew_period_periodYear_plural": "{{count}} ans", + "hosting_renew_period_currentPeriod": "Période actuelle : {{period}}", + "hosting_renew_period_noPeriodsAvailable": "Aucune période de renouvellement n'est disponible pour ce service.", + "hosting_renew_period_loadError": "Impossible de charger les périodes de renouvellement disponibles.", + "hosting_renew_period_noPeriod": "Aucune fréquence — renouvellement manuel", + "hosting_renew_period_noPeriodHint": "Attention, votre service ne sera pas renouvelé automatiquement.\nVous devrez le renouveler manuellement avant son expiration.", + "hosting_renew_period_or": "ou", + "hosting_renew_period_submit": "Valider", + "hosting_renew_period_cancel": "Annuler", + "hosting_renew_period_successToast": "La période de renouvellement a été mise à jour avec succès.", + "hosting_renew_period_errorToast": "Une erreur est survenue : {{error}}." +} diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_fr_FR.json b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_fr_FR.json new file mode 100644 index 000000000000..4bde7c3ad934 --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_fr_FR.json @@ -0,0 +1,18 @@ +{ + "hosting_renew_period_title": "Gérer le renouvellement", + "hosting_renew_period_description": "Sélectionnez la fréquence de renouvellement automatique souhaitée.", + "hosting_renew_period_periodMonth": "{{count}} mois", + "hosting_renew_period_periodMonth_plural": "{{count}} mois", + "hosting_renew_period_periodYear": "{{count}} an", + "hosting_renew_period_periodYear_plural": "{{count}} ans", + "hosting_renew_period_currentPeriod": "Période actuelle : {{period}}", + "hosting_renew_period_noPeriodsAvailable": "Aucune période de renouvellement n'est disponible pour ce service.", + "hosting_renew_period_loadError": "Impossible de charger les périodes de renouvellement disponibles.", + "hosting_renew_period_noPeriod": "Aucune fréquence — renouvellement manuel", + "hosting_renew_period_noPeriodHint": "Attention, votre service ne sera pas renouvelé automatiquement.\nVous devrez le renouveler manuellement avant son expiration.", + "hosting_renew_period_or": "ou", + "hosting_renew_period_submit": "Valider", + "hosting_renew_period_cancel": "Annuler", + "hosting_renew_period_successToast": "La période de renouvellement a été mise à jour avec succès.", + "hosting_renew_period_errorToast": "Une erreur est survenue : {{error}}." +} diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_it_IT.json b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_it_IT.json new file mode 100644 index 000000000000..c4c1ce198861 --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_it_IT.json @@ -0,0 +1,18 @@ +{ + "hosting_renew_period_title": "Gestire il rinnovo", + "hosting_renew_period_description": "Seleziona la frequenza di rinnovo automatico desiderata.", + "hosting_renew_period_periodMonth": "{{count}} mesi", + "hosting_renew_period_periodMonth_plural": "{{count}} mesi", + "hosting_renew_period_periodYear": "{{count}} anno", + "hosting_renew_period_periodYear_plural": "{{count}} anni", + "hosting_renew_period_currentPeriod": "Periodo attuale: {{period}}", + "hosting_renew_period_noPeriodsAvailable": "Nessun periodo di rinnovo disponibile per questo servizio.", + "hosting_renew_period_loadError": "Impossibile caricare i periodi di rinnovo disponibili.", + "hosting_renew_period_noPeriod": "Nessuna frequenza — rinnovo manuale", + "hosting_renew_period_noPeriodHint": "Attenzione, il tuo servizio non sarà rinnovato automaticamente. Dovrai rinnovarlo manualmente prima della scadenza.", + "hosting_renew_period_or": "o", + "hosting_renew_period_submit": "Conferma", + "hosting_renew_period_cancel": "Annulla", + "hosting_renew_period_successToast": "Il periodo di rinnovo è stato aggiornato con successo.", + "hosting_renew_period_errorToast": "Si è verificato un errore: {{error}}." +} diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_pl_PL.json b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_pl_PL.json new file mode 100644 index 000000000000..1470015b2877 --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_pl_PL.json @@ -0,0 +1,18 @@ +{ + "hosting_renew_period_title": "Zarządzaj odnawianiem", + "hosting_renew_period_description": "Wybierz pożądaną częstotliwość automatycznego odnawiania.", + "hosting_renew_period_periodMonth": "{{count}} miesięcy", + "hosting_renew_period_periodMonth_plural": "{{count}} miesięcy", + "hosting_renew_period_periodYear": "{{count}} rok", + "hosting_renew_period_periodYear_plural": "{{count}} lat", + "hosting_renew_period_currentPeriod": "Aktualny okres: {{period}}", + "hosting_renew_period_noPeriodsAvailable": "Brak dostępnych okresów odnawiania dla tej usługi.", + "hosting_renew_period_loadError": "Nie można załadować dostępnych okresów odnawiania.", + "hosting_renew_period_noPeriod": "Brak częstotliwości — odnawianie ręczne", + "hosting_renew_period_noPeriodHint": "Uwaga, Twoja usługa nie będzie odnawiana automatycznie. Będziesz musiał odnowić ją ręcznie przed jej wygaśnięciem.", + "hosting_renew_period_or": "lub", + "hosting_renew_period_submit": "Potwierdź", + "hosting_renew_period_cancel": "Anuluj", + "hosting_renew_period_successToast": "Okres odnawiania został pomyślnie zaktualizowany.", + "hosting_renew_period_errorToast": "Wystąpił błąd: {{error}}." +} diff --git a/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_pt_PT.json b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_pt_PT.json new file mode 100644 index 000000000000..fc1534153e13 --- /dev/null +++ b/packages/manager/apps/web/client/app/hosting/dashboard/renew-period/translations/Messages_pt_PT.json @@ -0,0 +1,18 @@ +{ + "hosting_renew_period_title": "Gerir a renovação", + "hosting_renew_period_description": "Selecione a frequência de renovação automática desejada.", + "hosting_renew_period_periodMonth": "{{count}} meses", + "hosting_renew_period_periodMonth_plural": "{{count}} meses", + "hosting_renew_period_periodYear": "{{count}} ano", + "hosting_renew_period_periodYear_plural": "{{count}} anos", + "hosting_renew_period_currentPeriod": "Período atual: {{period}}", + "hosting_renew_period_noPeriodsAvailable": "Não há período de renovação disponível para este serviço.", + "hosting_renew_period_loadError": "Impossível carregar os períodos de renovação disponíveis.", + "hosting_renew_period_noPeriod": "Nenhuma frequência — renovação manual", + "hosting_renew_period_noPeriodHint": "Atenção, o seu serviço não será renovado automaticamente. Terá de o renovar manualmente antes da sua expiração.", + "hosting_renew_period_or": "ou", + "hosting_renew_period_submit": "Validar", + "hosting_renew_period_cancel": "Anular", + "hosting_renew_period_successToast": "O período de renovação foi atualizado com sucesso.", + "hosting_renew_period_errorToast": "Ocorreu um erro: {{error}}." +} diff --git a/packages/manager/apps/web/client/app/hosting/general-informations/GENERAL_INFORMATIONS.controller.js b/packages/manager/apps/web/client/app/hosting/general-informations/GENERAL_INFORMATIONS.controller.js index fd8219ad8228..6babf6852b7d 100644 --- a/packages/manager/apps/web/client/app/hosting/general-informations/GENERAL_INFORMATIONS.controller.js +++ b/packages/manager/apps/web/client/app/hosting/general-informations/GENERAL_INFORMATIONS.controller.js @@ -36,6 +36,7 @@ export default class HostingGeneralInformationsCtrl { HostingRuntimes, hostingSSLCertificate, isChangeOfferFeatureAvailable, + isManageRenewFeatureAvailable, OvhApiScreenshot, user, ) { @@ -62,6 +63,7 @@ export default class HostingGeneralInformationsCtrl { this.HostingRuntimes = HostingRuntimes; this.hostingSSLCertificate = hostingSSLCertificate; this.isChangeOfferFeatureAvailable = isChangeOfferFeatureAvailable; + this.isManageRenewFeatureAvailable = isManageRenewFeatureAvailable; this.OvhApiScreenshot = OvhApiScreenshot; this.user = user; this.Domain = Domain; @@ -98,10 +100,16 @@ export default class HostingGeneralInformationsCtrl { this.videoCenterLink = `/beta/#/web-cloud/hosting/general/${this.serviceName}/general`; this.showVideoCenterTile = false; - const VIDEO_CENTER_INELIGIBLE_OFFERS = ['START_10_M', 'HOSTING_FREE_100_M', 'HOSTING_FREE_10_M', 'START_100_M']; + const VIDEO_CENTER_INELIGIBLE_OFFERS = [ + 'START_10_M', + 'HOSTING_FREE_100_M', + 'HOSTING_FREE_10_M', + 'START_100_M', + ]; const deregisterWatch = this.$scope.$watch('hosting.offer', (offer) => { if (!offer) return; - this.showVideoCenterTile = VIDEO_CENTER_INELIGIBLE_OFFERS.indexOf(offer.toUpperCase()) === -1; + this.showVideoCenterTile = + VIDEO_CENTER_INELIGIBLE_OFFERS.indexOf(offer.toUpperCase()) === -1; deregisterWatch(); if (this.showVideoCenterTile) { this.initializeVideoCenter(this.serviceName); @@ -180,13 +188,24 @@ export default class HostingGeneralInformationsCtrl { // v2 calls use Apiv2Service.httpApiv2 (handles /engine/api/v2 prefix) return this.$q .all([ - this.Apiv2Service.httpApiv2({ method: 'get', url: '/engine/api/v2/videocenter/resource' }) - .then(function(res) { return res.data || []; }) - .catch(function() { return []; }), + this.Apiv2Service.httpApiv2({ + method: 'get', + url: '/engine/api/v2/videocenter/resource', + }) + .then(function(res) { + return res.data || []; + }) + .catch(function() { + return []; + }), this.$http .get('/services', { params: { resourceName: serviceName } }) - .then(function(res) { return res.data || []; }) - .catch(function() { return []; }), + .then(function(res) { + return res.data || []; + }) + .catch(function() { + return []; + }), ]) .then((results) => { const allResources = results[0]; @@ -204,28 +223,45 @@ export default class HostingGeneralInformationsCtrl { return this.$http .get('/services', { params: { resourceName: resource.id } }) - .then(function(res) { return res.data || []; }) - .catch(function() { return []; }) + .then(function(res) { + return res.data || []; + }) + .catch(function() { + return []; + }) .then((vcServiceIds) => { if (!vcServiceIds.length) return null; return this.$http .get(`/services/${vcServiceIds[0]}`) - .then(function(res) { return res.data; }) - .catch(function() { return null; }); + .then(function(res) { + return res.data; + }) + .catch(function() { + return null; + }); }) .then((vcService) => { - if (!vcService || vcService.parentServiceId !== hostingServiceId) { + if ( + !vcService || + vcService.parentServiceId !== hostingServiceId + ) { return null; } return this.Apiv2Service.httpApiv2({ method: 'get', url: `/engine/api/v2/videocenter/resource/${resource.id}`, }) - .then(function(res) { return res.data; }) - .catch(function() { return null; }) + .then(function(res) { + return res.data; + }) + .catch(function() { + return null; + }) .then((detail) => { const offerName = - detail && detail.currentState && detail.currentState.offerName; + detail && + detail.currentState && + detail.currentState.offerName; if (!offerName) { return { status: 'freemium', plan: null }; } diff --git a/packages/manager/apps/web/client/app/hosting/general-informations/GENERAL_INFORMATIONS.html b/packages/manager/apps/web/client/app/hosting/general-informations/GENERAL_INFORMATIONS.html index 0329a53a1b9a..297dc31b50ce 100644 --- a/packages/manager/apps/web/client/app/hosting/general-informations/GENERAL_INFORMATIONS.html +++ b/packages/manager/apps/web/client/app/hosting/general-informations/GENERAL_INFORMATIONS.html @@ -809,6 +809,15 @@ data-ng-bind="::hosting.creation|date:'mediumDate'" > + + + diff --git a/packages/manager/apps/web/client/app/hosting/general-informations/general-informations.routing.js b/packages/manager/apps/web/client/app/hosting/general-informations/general-informations.routing.js index efdde47ad763..93629b65c22f 100644 --- a/packages/manager/apps/web/client/app/hosting/general-informations/general-informations.routing.js +++ b/packages/manager/apps/web/client/app/hosting/general-informations/general-informations.routing.js @@ -15,6 +15,14 @@ export default /* @ngInject */ ($stateProvider) => { 'web-hosting:change-offer', ); }), + isManageRenewFeatureAvailable: /* @ngInject */ (ovhFeatureFlipping) => + ovhFeatureFlipping + .checkFeatureAvailability('web-hosting:manage-renew') + .then((featureAvailability) => { + return featureAvailability.isFeatureAvailable( + 'web-hosting:manage-renew', + ); + }), breadcrumb: () => null, }, }); diff --git a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_de_DE.json b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_de_DE.json index 1feb294d518d..1c16f6f54563 100644 --- a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_de_DE.json +++ b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_de_DE.json @@ -11,5 +11,6 @@ "hosting_dashboard_service_video_center_freemium": "Aktiv - Freemium", "hosting_dashboard_service_video_center_freemium_paid": "Aktiv - Freemium + {{plan}}", "hosting_dashboard_service_video_center_plan_pro": "Videozentrum Pro", - "hosting_dashboard_service_video_center_plan_plus": "Videozentrum Plus" + "hosting_dashboard_service_video_center_plan_plus": "Videozentrum Plus", + "hosting_dashboard_manage_renewal_button": "Die Verwaltung der Erneuerung" } diff --git a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_en_GB.json b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_en_GB.json index 1be9f0df49fd..42205375840f 100644 --- a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_en_GB.json +++ b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_en_GB.json @@ -11,5 +11,6 @@ "hosting_dashboard_service_video_center_freemium": "Active - Freemium", "hosting_dashboard_service_video_center_freemium_paid": "Active - Freemium + {{plan}}", "hosting_dashboard_service_video_center_plan_pro": "Video Centre Pro", - "hosting_dashboard_service_video_center_plan_plus": "Video Centre Plus" + "hosting_dashboard_service_video_center_plan_plus": "Video Centre Plus", + "hosting_dashboard_manage_renewal_button": "Manage the renewal" } diff --git a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_es_ES.json b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_es_ES.json index 3eb8e0bc801e..04804cff2a49 100644 --- a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_es_ES.json +++ b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_es_ES.json @@ -11,5 +11,6 @@ "hosting_dashboard_service_video_center_freemium": "Activo - Freemium", "hosting_dashboard_service_video_center_freemium_paid": "Activo - Freemium + {{plan}}", "hosting_dashboard_service_video_center_plan_pro": "Centro de Vídeo Pro", - "hosting_dashboard_service_video_center_plan_plus": "Centro de Vídeo Plus" + "hosting_dashboard_service_video_center_plan_plus": "Centro de Vídeo Plus", + "hosting_dashboard_manage_renewal_button": "Gestionar la renovación" } diff --git a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_fr_CA.json b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_fr_CA.json index 3dd9a3081ecf..cfba1e7a30c2 100644 --- a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_fr_CA.json +++ b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_fr_CA.json @@ -11,5 +11,6 @@ "hosting_dashboard_service_video_center_freemium": "Actif - Freemium", "hosting_dashboard_service_video_center_freemium_paid": "Actif - Freemium + {{plan}}", "hosting_dashboard_service_video_center_plan_pro": "Video Center Pro", - "hosting_dashboard_service_video_center_plan_plus": "Video Center Plus" + "hosting_dashboard_service_video_center_plan_plus": "Video Center Plus", + "hosting_dashboard_manage_renewal_button": "Gérer le renouvellement" } diff --git a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_fr_FR.json b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_fr_FR.json index 3dd9a3081ecf..cfba1e7a30c2 100644 --- a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_fr_FR.json +++ b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_fr_FR.json @@ -11,5 +11,6 @@ "hosting_dashboard_service_video_center_freemium": "Actif - Freemium", "hosting_dashboard_service_video_center_freemium_paid": "Actif - Freemium + {{plan}}", "hosting_dashboard_service_video_center_plan_pro": "Video Center Pro", - "hosting_dashboard_service_video_center_plan_plus": "Video Center Plus" + "hosting_dashboard_service_video_center_plan_plus": "Video Center Plus", + "hosting_dashboard_manage_renewal_button": "Gérer le renouvellement" } diff --git a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_it_IT.json b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_it_IT.json index 5e323ebd9545..1616c16e7617 100644 --- a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_it_IT.json +++ b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_it_IT.json @@ -11,5 +11,6 @@ "hosting_dashboard_service_video_center_freemium": "Attivo - Freemium", "hosting_dashboard_service_video_center_freemium_paid": "Attivo - Freemium + {{plan}}", "hosting_dashboard_service_video_center_plan_pro": "Video Center Pro", - "hosting_dashboard_service_video_center_plan_plus": "Video Center Plus" + "hosting_dashboard_service_video_center_plan_plus": "Video Center Plus", + "hosting_dashboard_manage_renewal_button": "Gestire il rinnovo" } diff --git a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_pl_PL.json b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_pl_PL.json index dc34641d1b09..05a91804beb3 100644 --- a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_pl_PL.json +++ b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_pl_PL.json @@ -11,5 +11,6 @@ "hosting_dashboard_service_video_center_freemium": "Aktywny - Freemium", "hosting_dashboard_service_video_center_freemium_paid": "Aktywny - Freemium + {{plan}}", "hosting_dashboard_service_video_center_plan_pro": "Centrum Wideo Pro", - "hosting_dashboard_service_video_center_plan_plus": "Centrum Wideo Plus" + "hosting_dashboard_service_video_center_plan_plus": "Centrum Wideo Plus", + "hosting_dashboard_manage_renewal_button": "Zarządzanie odnawianiem" } diff --git a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_pt_PT.json b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_pt_PT.json index 6b6a8a593d28..b1b3cd899969 100644 --- a/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_pt_PT.json +++ b/packages/manager/apps/web/client/app/hosting/general-informations/translations/Messages_pt_PT.json @@ -11,5 +11,6 @@ "hosting_dashboard_service_video_center_freemium": "Ativo - Freemium", "hosting_dashboard_service_video_center_freemium_paid": "Ativo - Freemium + {{plan}}", "hosting_dashboard_service_video_center_plan_pro": "Centro de Vídeo Pro", - "hosting_dashboard_service_video_center_plan_plus": "Centro de Vídeo Plus" + "hosting_dashboard_service_video_center_plan_plus": "Centro de Vídeo Plus", + "hosting_dashboard_manage_renewal_button": "Gerir a renovação" }