Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -48,6 +49,7 @@ angular
userLogs,
hostingDomainOffersComponent,
hostingAbuseUnblock,
hostingRenewPeriod,
])
.config(routing)
.run(/* @ngTranslationsInject:json ./translations */);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import template from './renew-period.html';
import controller from './renew-period.controller';

export default {
bindings: {
goBack: '<',
serviceId: '<',
serviceName: '<',
},
template,
controller,
};
Original file line number Diff line number Diff line change
@@ -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,
};
Original file line number Diff line number Diff line change
@@ -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',
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<oui-modal
data-heading="{{ 'hosting_renew_period_title' | translate }}"
data-primary-action="$ctrl.submit()"
data-primary-disabled="!$ctrl.hasSelection() || $ctrl.isSubmitting || $ctrl.isLoading"
data-primary-label="{{ 'hosting_renew_period_submit' | translate }}"
data-secondary-action="$ctrl.cancel()"
data-secondary-label="{{ 'hosting_renew_period_cancel' | translate }}"
data-on-dismiss="$ctrl.cancel()"
data-loading="$ctrl.isSubmitting"
>
<div>
<p
class="mb-3"
data-translate="hosting_renew_period_description"
></p>

<div data-ng-if="$ctrl.isLoading" class="text-center">
<oui-spinner></oui-spinner>
</div>

<div
data-ng-if="!$ctrl.isLoading && $ctrl.loadError"
class="oui-message oui-message_warning"
data-translate="hosting_renew_period_loadError"
></div>

<div
data-ng-if="!$ctrl.isLoading && !$ctrl.loadError && $ctrl.availableMonths.length === 0 && !$ctrl.manualModeAvailable"
class="oui-message oui-message_warning"
data-translate="hosting_renew_period_noPeriodsAvailable"
></div>

<div data-ng-if="!$ctrl.isLoading && !$ctrl.loadError && ($ctrl.availableMonths.length > 0 || $ctrl.manualModeAvailable)">
<div
data-ng-if="$ctrl.availableMonths.length > 0"
class="d-flex flex-wrap"
style="gap: 0.5rem;"
>
<button
type="button"
class="oui-button oui-button_secondary"
data-ng-class="{ 'oui-button_primary': $ctrl.isMonthsSelected(months) }"
data-ng-repeat="months in $ctrl.availableMonths track by months"
data-ng-click="$ctrl.selectMonths(months)"
data-ng-disabled="$ctrl.isSubmitting"
>
<span data-ng-bind="$ctrl.periodLabel(months)"></span>
<span
class="d-block oui-text_s"
data-ng-if="$ctrl.isUpfront && $ctrl.optionsByMonths[months].price.text"
data-ng-bind="$ctrl.optionsByMonths[months].price.text"
></span>
</button>
</div>

<div
class="d-flex align-items-center mt-3"
style="gap: 0.5rem;"
data-ng-if="$ctrl.currentPeriod && !$ctrl.isCurrentlyManual"
>
<span
class="oui-icon oui-icon-info-circle"
aria-hidden="true"
></span>
<span
class="mb-0 oui-text_s"
data-translate="hosting_renew_period_currentPeriod"
data-translate-values="{ period: $ctrl.periodLabel($ctrl.currentPeriod) }"
></span>
</div>

<div
data-ng-if="$ctrl.manualModeAvailable && !$ctrl.isUpfront"
class="mt-3"
>
<div
data-ng-if="$ctrl.availableMonths.length > 0"
class="d-flex align-items-center mb-3"
style="gap: 0.75rem;"
>
<span style="flex: 1; height: 1px; background-color: #e0e0e0;"></span>
<span
class="oui-text_s"
data-translate="hosting_renew_period_or"
></span>
<span style="flex: 1; height: 1px; background-color: #e0e0e0;"></span>
</div>

<button
type="button"
class="oui-box oui-box_light d-block w-100 text-left"
style="cursor: pointer; padding: 1rem 1.5rem;"
data-ng-style="$ctrl.isManualSelected ? { 'border-color': '#0050d7', 'background-color': '#f5f9ff' } : {}"
data-ng-click="$ctrl.selectManual()"
data-ng-disabled="$ctrl.isSubmitting"
>
<span
class="d-block"
style="font-weight: 600;"
data-translate="hosting_renew_period_noPeriod"
></span>
<span
class="d-block mt-1 oui-text_s"
style="white-space: pre-line;"
data-translate="hosting_renew_period_noPeriodHint"
></span>
</button>
</div>
</div>
</div>
</oui-modal>
Loading
Loading