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"
}