@financial-times/n-conversion-forms 23.2.0 → 24.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,8 @@ var _propTypes = _interopRequireDefault(require("prop-types"));
15
15
 
16
16
  var _classnames = _interopRequireDefault(require("classnames"));
17
17
 
18
+ var _nPricing = require("@financial-times/n-pricing");
19
+
18
20
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
19
21
 
20
22
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
@@ -32,13 +34,54 @@ function PaymentTerm(_ref) {
32
34
  options = _ref$options === void 0 ? [] : _ref$options,
33
35
  _ref$isFixedTermOffer = _ref.isFixedTermOffer,
34
36
  isFixedTermOffer = _ref$isFixedTermOffer === void 0 ? false : _ref$isFixedTermOffer,
35
- displayName = _ref.displayName,
37
+ offerDisplayName = _ref.offerDisplayName,
36
38
  _ref$showLegal = _ref.showLegal,
37
39
  showLegal = _ref$showLegal === void 0 ? true : _ref$showLegal,
38
40
  _ref$largePrice = _ref.largePrice,
39
41
  largePrice = _ref$largePrice === void 0 ? false : _ref$largePrice,
40
42
  _ref$optionsInARow = _ref.optionsInARow,
41
43
  optionsInARow = _ref$optionsInARow === void 0 ? false : _ref$optionsInARow;
44
+
45
+ /**
46
+ * Compute monthly price for given term name
47
+ * @param {number} amount price in number format
48
+ * @param {string} currency country id of the currency
49
+ * @param {string} period PxY (yearly) or PxM (montly) where x is the amount of years/months
50
+ * @returns {string}
51
+ */
52
+ var getMontlyPriceFromPeriod = function getMontlyPriceFromPeriod(amount, currency, period) {
53
+ var periodObj = new _nPricing.Period(period);
54
+ var monthlyPrice = periodObj.calculatePrice('P1M', amount);
55
+ return new _nPricing.Monthly({
56
+ value: monthlyPrice,
57
+ currency: currency
58
+ }).getAmount('monthly');
59
+ };
60
+ /**
61
+ * returns period converted to time if found
62
+ * otherwise returns empty string to avoid show information not mapped
63
+ * @param {string} period PxY (yearly) or PxM (montly) where x is the amount of years/months
64
+ * @returns {string}
65
+ */
66
+
67
+
68
+ var getTimeFromPeriod = function getTimeFromPeriod(period) {
69
+ var freq = period.substring(period.length - 1) === 'Y' ? 'years' : 'months';
70
+ var amount = period.substring(1, period.length - 1);
71
+ return period ? "".concat(amount, " ").concat(freq) : '';
72
+ };
73
+
74
+ var isValidPeriod = function isValidPeriod(period) {
75
+ try {
76
+ // Period should throw an error if it is not properly provided
77
+ // in order to validate it, we just send in case type is string
78
+ new _nPricing.Period(typeof period === 'string' ? period : '');
79
+ return true;
80
+ } catch (e) {
81
+ return false;
82
+ }
83
+ };
84
+
42
85
  var nameMap = {
43
86
  annual: {
44
87
  title: 'Annual',
@@ -103,6 +146,30 @@ function PaymentTerm(_ref) {
103
146
  className: "ncf__payment-term__renews-text"
104
147
  }, textToDisplay);
105
148
  }
149
+ },
150
+ custom: {
151
+ price: function price(_price4) {
152
+ return /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, "Single", ' ', /*#__PURE__*/_react["default"].createElement("span", {
153
+ className: "ncf__payment-term__price ncf__strong"
154
+ }, _price4), ' ', "payment");
155
+ },
156
+ trialPrice: function trialPrice(_trialPrice, trialPeriod) {
157
+ return /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, "Unless you cancel during your trial you will be billed", ' ', /*#__PURE__*/_react["default"].createElement("span", {
158
+ className: "ncf__payment-term__price"
159
+ }, _trialPrice), " per ", trialPeriod, "after the trial period.");
160
+ },
161
+ monthlyPrice: function monthlyPrice(_monthlyPrice) {
162
+ return Boolean(_monthlyPrice) && /*#__PURE__*/_react["default"].createElement("span", {
163
+ className: "ncf__payment-term__equivalent-price"
164
+ }, "That\u2019s equivalent to", ' ', /*#__PURE__*/_react["default"].createElement("span", {
165
+ className: "ncf__payment-term__monthly-price"
166
+ }, _monthlyPrice), ' ', "per month");
167
+ },
168
+ renewsText: function renewsText(renewalPeriod) {
169
+ return Boolean(renewalPeriod) && /*#__PURE__*/_react["default"].createElement("p", {
170
+ className: "ncf__payment-term__renews-text"
171
+ }, "Renews every ", renewalPeriod, " unless cancelled");
172
+ }
106
173
  }
107
174
  };
108
175
 
@@ -122,10 +189,6 @@ function PaymentTerm(_ref) {
122
189
  defaultChecked: true
123
190
  });
124
191
 
125
- var showTrialCopyInTitle = option.isTrial && !isPrintOrBundle && !isEpaper;
126
- var defaultTitle = option.name ? nameMap[option.name].title : '';
127
- var title = isFixedTermOffer ? "".concat(displayName, " - ").concat(defaultTitle) : defaultTitle;
128
-
129
192
  var createDiscount = function createDiscount() {
130
193
  return option.discount && /*#__PURE__*/_react["default"].createElement("span", {
131
194
  className: "ncf__payment-term__discount"
@@ -139,23 +202,46 @@ function PaymentTerm(_ref) {
139
202
  className: "ncf__payment-term__trial-price"
140
203
  }, option.trialPrice), /*#__PURE__*/_react["default"].createElement("br", null), nameMap[option.name] && nameMap[option.name].trialPrice(option.price)) : /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, nameMap[option.name] ? /*#__PURE__*/_react["default"].createElement("div", {
141
204
  className: "ncf__payment-term__description"
142
- }, nameMap[option.name].price(option.price), nameMap[option.name].monthlyPrice(option.monthlyPrice), nameMap[option.name].renewsText(isFixedTermOffer)) : /*#__PURE__*/_react["default"].createElement("div", null, /*#__PURE__*/_react["default"].createElement("span", {
205
+ }, nameMap[option.name].price(option.price), nameMap[option.name].monthlyPrice(option.monthlyPrice), nameMap[option.name].renewsText(isFixedTermOffer)) : // this should cover the cases different than annual, quarterly and monthly
206
+ // for those containing period on option.value, render custom template, for the rest keep legacy render
207
+ isValidPeriod(option.value) ? /*#__PURE__*/_react["default"].createElement("div", {
208
+ className: "ncf__payment-term__description"
209
+ }, nameMap['custom'].price(option.price), nameMap['custom'].monthlyPrice(option.monthlyPrice && option.monthlyPrice !== '0' ? Number(option.monthlyPrice) : getMontlyPriceFromPeriod(option.amount, option.currency, option.value)), nameMap['custom'].renewsText(getTimeFromPeriod(option.value))) : /*#__PURE__*/_react["default"].createElement("div", null, /*#__PURE__*/_react["default"].createElement("span", {
143
210
  className: largePrice ? 'ncf__payment-term__large-price' : ''
144
211
  }, option.price), option.chargeOnText && /*#__PURE__*/_react["default"].createElement("p", {
145
212
  className: "ncf__payment-term__charge-on-text"
146
213
  }, option.chargeOnText)));
147
214
  };
148
215
 
149
- var getDisplayName = function getDisplayName() {
150
- var displayName = '';
216
+ var getTermDisplayName = function getTermDisplayName() {
217
+ var showTrialCopyInTitle = option.isTrial && !isPrintOrBundle && !isEpaper;
218
+ var defaultTitle = option.name && nameMap[option.name] ? nameMap[option.name].title : '';
219
+ var title = isFixedTermOffer ? "".concat(offerDisplayName, " - ").concat(defaultTitle) : defaultTitle;
220
+ var termDisplayName = '';
151
221
 
152
222
  if (showTrialCopyInTitle) {
153
223
  var termName = option.displayName ? option.displayName : 'Premium Digital';
154
- displayName = "Trial: ".concat(termName, " - ");
224
+ termDisplayName = "Trial: ".concat(termName, " - ");
155
225
  }
156
226
 
157
- var termPeriod = nameMap[option.name] ? title : option.title;
158
- return "".concat(displayName).concat(termPeriod, " ");
227
+ var getTermPeriod = function getTermPeriod() {
228
+ // annual, quarterly and monthly
229
+ if (nameMap[option.name]) {
230
+ return title;
231
+ } // custom offer with period provided
232
+
233
+
234
+ if (isValidPeriod(option.value)) {
235
+ return getTimeFromPeriod(option.value);
236
+ } // custom legacy cases, where period is not provided
237
+
238
+
239
+ return option.title;
240
+ };
241
+
242
+ var termPeriod = getTermPeriod();
243
+ termDisplayName = "".concat(termDisplayName).concat(termPeriod, " ");
244
+ return termDisplayName;
159
245
  };
160
246
 
161
247
  return /*#__PURE__*/_react["default"].createElement("div", {
@@ -168,7 +254,7 @@ function PaymentTerm(_ref) {
168
254
  className: (0, _classnames["default"])(['ncf__payment-term__title', {
169
255
  'ncf__payment-term__title--large-price': largePrice
170
256
  }])
171
- }, getDisplayName(), option.subTitle && /*#__PURE__*/_react["default"].createElement("span", {
257
+ }, getTermDisplayName(), option.subTitle && /*#__PURE__*/_react["default"].createElement("span", {
172
258
  className: "ncf__regular ncf__payment-term__sub-title"
173
259
  }, option.subTitle)), createDescription()));
174
260
  };
@@ -210,7 +296,7 @@ PaymentTerm.propTypes = {
210
296
  selected: _propTypes["default"].bool,
211
297
  trialDuration: _propTypes["default"].string,
212
298
  trialPrice: _propTypes["default"].string,
213
- amount: _propTypes["default"].number,
299
+ amount: _propTypes["default"].string,
214
300
  trialAmount: _propTypes["default"].number,
215
301
  value: _propTypes["default"].string.isRequired,
216
302
  monthlyPrice: _propTypes["default"].string,
@@ -220,7 +306,7 @@ PaymentTerm.propTypes = {
220
306
  chargeOnText: _propTypes["default"].string
221
307
  })),
222
308
  isFixedTermOffer: _propTypes["default"].bool,
223
- displayName: _propTypes["default"].string,
309
+ offerDisplayName: _propTypes["default"].string,
224
310
  showLegal: _propTypes["default"].bool,
225
311
  largePrice: _propTypes["default"].bool,
226
312
  optionsInARow: _propTypes["default"].bool
@@ -25,10 +25,19 @@ var SOCIALS = [{
25
25
  name: 'facebook',
26
26
  link: 'https://www.facebook.com/financialtimes'
27
27
  }];
28
+ var STORES = [{
29
+ name: 'apple',
30
+ link: 'https://apps.apple.com/app/apple-store/id1200842933'
31
+ }, {
32
+ name: 'android',
33
+ link: 'https://play.google.com/store/apps/details?id=com.ft.news'
34
+ }];
28
35
 
29
36
  function RegistrationConfirmation(_ref) {
30
37
  var _ref$email = _ref.email,
31
- email = _ref$email === void 0 ? EMAIL_DEFAULT_TEXT : _ref$email;
38
+ email = _ref$email === void 0 ? EMAIL_DEFAULT_TEXT : _ref$email,
39
+ _ref$returnUrl = _ref.returnUrl,
40
+ returnUrl = _ref$returnUrl === void 0 ? '/' : _ref$returnUrl;
32
41
  return /*#__PURE__*/_react["default"].createElement("div", {
33
42
  className: "ncf"
34
43
  }, /*#__PURE__*/_react["default"].createElement("div", {
@@ -38,7 +47,7 @@ function RegistrationConfirmation(_ref) {
38
47
  }), /*#__PURE__*/_react["default"].createElement("h1", {
39
48
  className: "ncf__header ncf__header--confirmation"
40
49
  }, "Success"), /*#__PURE__*/_react["default"].createElement("p", {
41
- className: "ncf__paragraph ncf__confirmation--message"
50
+ className: "ncf__confirmation--message"
42
51
  }, "We\u2019ve sent confirmation to ", email, ".")), /*#__PURE__*/_react["default"].createElement("div", {
43
52
  className: "ncf__divider-horizontal"
44
53
  }), /*#__PURE__*/_react["default"].createElement("div", {
@@ -47,9 +56,11 @@ function RegistrationConfirmation(_ref) {
47
56
  className: "ncf__confirmation--socials"
48
57
  }, /*#__PURE__*/_react["default"].createElement("b", null, "Breaking news alerts, direct to your lock screen"), /*#__PURE__*/_react["default"].createElement("p", {
49
58
  className: "ncf__confirmation--mobile"
50
- }, "Find us in the app and google play stores or follow us on our socials"), /*#__PURE__*/_react["default"].createElement("p", {
59
+ }, "Download our apps or follow us on our socials"), /*#__PURE__*/_react["default"].createElement("p", {
51
60
  className: "ncf__confirmation--desktop"
52
- }, "Find us in the app and google play stores or follow us on our socials"), /*#__PURE__*/_react["default"].createElement("section", {
61
+ }, "Find us in the app and google play stores or follow us on our socials"), /*#__PURE__*/_react["default"].createElement("div", {
62
+ className: "ncf__confirmation--badges"
63
+ }, /*#__PURE__*/_react["default"].createElement("div", {
53
64
  className: "o-social-follow",
54
65
  "aria-label": "Follow on social media"
55
66
  }, SOCIALS.map(function (_ref2) {
@@ -65,17 +76,33 @@ function RegistrationConfirmation(_ref) {
65
76
  }, /*#__PURE__*/_react["default"].createElement("span", {
66
77
  className: "o-social-follow-icon__label"
67
78
  }, "on ", name));
68
- }))), /*#__PURE__*/_react["default"].createElement("div", {
79
+ })), /*#__PURE__*/_react["default"].createElement("div", {
80
+ className: "ncf__confirmation--mobile ncf__confirmation--app-badges",
81
+ "aria-label": "Download the app"
82
+ }, STORES.map(function (_ref3) {
83
+ var name = _ref3.name,
84
+ link = _ref3.link;
85
+ return /*#__PURE__*/_react["default"].createElement("a", {
86
+ key: name,
87
+ href: link,
88
+ className: "ncf-icon ncf-icon--".concat(name),
89
+ target: "_blank",
90
+ rel: "noopener noreferrer",
91
+ "data-trackable": "register-app-".concat(name)
92
+ }, /*#__PURE__*/_react["default"].createElement("span", {
93
+ className: "o-social-follow-icon__label"
94
+ }, "on ", name));
95
+ })))), /*#__PURE__*/_react["default"].createElement("div", {
69
96
  className: "ncf__confirmation--alerts"
70
97
  }, /*#__PURE__*/_react["default"].createElement("b", null, "Set up email alerts in", /*#__PURE__*/_react["default"].createElement("span", {
71
98
  "aria-label": "myFT",
72
- className: "icon-myft"
99
+ className: "ncf-icon ncf-icon--myft"
73
100
  })), /*#__PURE__*/_react["default"].createElement("p", null, "Choose the content you want to follow by personalising your alerts for the most important topics and additional newsletters"), /*#__PURE__*/_react["default"].createElement("a", {
74
- href: '/myft',
101
+ href: "/myft",
75
102
  className: "margin-top-x4 ncf__button ncf__button--secondary",
76
103
  "data-trackable": "register-personalise-my-alerts"
77
104
  }, "Personalise my alerts"))), /*#__PURE__*/_react["default"].createElement("a", {
78
- href: '/',
105
+ href: returnUrl,
79
106
  target: "_parent",
80
107
  className: "ncf__confirmation--finish ncf__button ncf__button--submit",
81
108
  "data-trackable": "register-finish"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@financial-times/n-conversion-forms",
3
- "version": "23.2.0",
3
+ "version": "24.2.0",
4
4
  "description": "Containing jsx components and styles for forms included on Accounts and Acqusition apps (next-signup, next-profile, next-retention, etc).",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -40,6 +40,7 @@
40
40
  "@financial-times/eslint-config-next": "^3.0.0",
41
41
  "@financial-times/jest-browser-resolver": "^1.0.2",
42
42
  "@financial-times/n-gage": "^8.2.0",
43
+ "@financial-times/n-pricing": "5.5.0",
43
44
  "@storybook/addon-a11y": "^6.0.10",
44
45
  "@storybook/addon-essentials": "6.0.10",
45
46
  "@storybook/react": "^6.0.10",
@@ -20,13 +20,13 @@
20
20
  }
21
21
 
22
22
  &--desktop {
23
- @include oGridRespondTo($until: L) {
23
+ @include oGridRespondTo($until: M) {
24
24
  display: none;
25
25
  }
26
26
  }
27
27
 
28
28
  &--mobile {
29
- @include oGridRespondTo(L) {
29
+ @include oGridRespondTo(M) {
30
30
  display: none;
31
31
  }
32
32
  }
@@ -34,9 +34,10 @@
34
34
  &--links {
35
35
  display: grid;
36
36
  grid-template-rows: 1fr 1fr;
37
- gap: oSpacingByName('s6');
37
+ gap: oSpacingByName('s4');
38
+ margin-top: oSpacingByName('s6');
38
39
 
39
- @media (min-width: 980px) {
40
+ @include oGridRespondTo(M) {
40
41
  grid-template-rows: unset;
41
42
  grid-template-columns: 1fr 1fr;
42
43
  gap: oSpacingByName('s8');
@@ -44,15 +45,33 @@
44
45
  }
45
46
 
46
47
  &--socials {
47
- @media (min-width: 980px) {
48
+ @include oGridRespondTo(M) {
48
49
  padding: oSpacingByName('s6') oSpacingByName('s4');
49
50
  }
50
51
  }
51
52
 
53
+ &--badges {
54
+ @include oGridRespondTo($until: M) {
55
+ display: grid;
56
+ grid-template-columns: 1fr 1fr;
57
+ gap: oSpacingByName('s6');
58
+ margin: auto;
59
+ max-width: 256px;
60
+ }
61
+ }
62
+
63
+ &--app-badges {
64
+ @include oGridRespondTo($until: M) {
65
+ display: flex;
66
+ flex-direction: column;
67
+ gap: 16px;
68
+ }
69
+ }
70
+
52
71
  &--alerts {
53
72
  display: flex;
54
73
  flex-direction: column;
55
- padding: oSpacingByName('s6') oSpacingByName('s4');
74
+ padding: 24px 20px;
56
75
  background-color: oColorsByName('white-60');
57
76
 
58
77
  a {
@@ -65,24 +84,39 @@
65
84
 
66
85
  &--finish {
67
86
  display: block;
68
- margin: 4rem auto 0;
87
+ margin: 48px auto 0;
69
88
  width: 100%;
70
89
 
71
- @media (min-width: 740px) {
90
+ @include oGridRespondTo(M) {
72
91
  max-width: 260px;
73
92
  }
74
93
  }
75
94
  }
76
95
  }
77
96
 
78
- .icon-myft::before {
97
+ .ncf-icon::before {
79
98
  content: '';
80
99
  display: inline-block;
81
- margin-left: 0.5rem;
82
- height: 1rem;
83
- width: 3rem;
84
100
  vertical-align: middle;
85
101
  background-size: contain;
86
102
  background-repeat: no-repeat;
103
+ }
104
+
105
+ .ncf-icon--myft::before {
106
+ margin-left: 0.5rem;
107
+ height: 1rem;
108
+ width: 3rem;
87
109
  background-image: url('https://www.ft.com/__origami/service/image/v2/images/raw/ftlogo:brand-myft?format=svg&source=next-subscribe&tint=%2333302E%2C%2333302E&width=28&height=16');
88
110
  }
111
+
112
+ .ncf-icon--android::before {
113
+ height: 40px;
114
+ width: 136px;
115
+ background-image: url('https://www.ft.com/__origami/service/image/v2/images/raw/app-badge-v1%3Aandroid?source=next-subscribe');
116
+ }
117
+
118
+ .ncf-icon--apple::before {
119
+ height: 40px;
120
+ width: 136px;
121
+ background-image: url('https://www.ft.com/__origami/service/image/v2/images/raw/app-badge-v1%3Aapple?format=svg&source=next-subscribe');
122
+ }
@@ -1,3 +1,8 @@
1
+ const {
2
+ Period,
3
+ Monthly,
4
+ } = require('@financial-times/n-pricing');
5
+
1
6
  /**
2
7
  * Utility for the `n-conversion-forms/partial/payment-term.html` partial
3
8
  * @example
@@ -75,6 +80,23 @@ class PaymentTerm {
75
80
  return this.$paymentTerm.addEventListener('change', callback);
76
81
  }
77
82
 
83
+ isValidPeriod (period) {
84
+ try {
85
+ // Period should throw an error if it is not properly provided
86
+ // in order to validate it, we just send in case type is string
87
+ new Period(typeof period === 'string' ? period : '');
88
+ return true;
89
+ } catch (e) {
90
+ return false;
91
+ }
92
+ };
93
+
94
+ getMontlyPriceFromPeriod (amount, currency, period) {
95
+ const periodObj = new Period(period);
96
+ const monthlyPrice = periodObj.calculatePrice('P1M', amount);
97
+ return new Monthly({ value: monthlyPrice, currency }).getAmount('monthly');
98
+ }
99
+
78
100
  /**
79
101
  * Update the payment term options
80
102
  * @param {Array} options Array of objects contain terms information
@@ -104,7 +126,9 @@ class PaymentTerm {
104
126
  trialPrice.innerHTML = update.trialPrice;
105
127
  }
106
128
  if (monthlyPrice) {
107
- monthlyPrice.innerHTML = update.monthlyPrice;
129
+ monthlyPrice.innerHTML = this.isValidPeriod(update.value) ?
130
+ this.getMontlyPriceFromPeriod(update.amount, update.currency, update.value) :
131
+ update.monthlyPrice;
108
132
  }
109
133
  }
110
134
  }