@automattic/plans-grid-next 1.0.2 → 1.0.3

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.
Files changed (298) hide show
  1. package/CHANGELOG.md +4 -1
  2. package/dist/cjs/_shared.scss +4 -3
  3. package/dist/cjs/components/comparison-grid/index.js +99 -92
  4. package/dist/cjs/components/comparison-grid/index.js.map +1 -1
  5. package/dist/cjs/components/comparison-grid/style.scss +10 -2
  6. package/dist/cjs/components/features-grid/client-logo-list/client-list.js +0 -12
  7. package/dist/cjs/components/features-grid/client-logo-list/client-list.js.map +1 -1
  8. package/dist/cjs/components/features-grid/index.js +9 -6
  9. package/dist/cjs/components/features-grid/index.js.map +1 -1
  10. package/dist/cjs/components/features-grid/plan-features-list.js +10 -3
  11. package/dist/cjs/components/features-grid/plan-features-list.js.map +1 -1
  12. package/dist/cjs/components/features-grid/plan-headers.js +2 -2
  13. package/dist/cjs/components/features-grid/plan-headers.js.map +1 -1
  14. package/dist/cjs/components/features-grid/plan-tagline.js +1 -1
  15. package/dist/cjs/components/features-grid/plan-tagline.js.map +1 -1
  16. package/dist/cjs/components/features-grid/style.scss +107 -19
  17. package/dist/cjs/components/features-grid/table.js +1 -1
  18. package/dist/cjs/components/features-grid/table.js.map +1 -1
  19. package/dist/cjs/components/features.js +43 -4
  20. package/dist/cjs/components/features.js.map +1 -1
  21. package/dist/cjs/components/item.js +1 -1
  22. package/dist/cjs/components/item.js.map +1 -1
  23. package/dist/cjs/components/plan-button/index.js +5 -3
  24. package/dist/cjs/components/plan-button/index.js.map +1 -1
  25. package/dist/cjs/components/plan-button/style.scss +75 -51
  26. package/dist/cjs/components/plan-div-td-container.js +4 -1
  27. package/dist/cjs/components/plan-div-td-container.js.map +1 -1
  28. package/dist/cjs/components/plan-logo.js +6 -3
  29. package/dist/cjs/components/plan-logo.js.map +1 -1
  30. package/dist/cjs/components/plan-type-selector/components/interval-type-dropdown.js +12 -1
  31. package/dist/cjs/components/plan-type-selector/components/interval-type-dropdown.js.map +1 -1
  32. package/dist/cjs/components/plan-type-selector/hooks/use-max-discount.js +4 -33
  33. package/dist/cjs/components/plan-type-selector/hooks/use-max-discount.js.map +1 -1
  34. package/dist/cjs/components/plan-type-selector/hooks/use-max-discounts-for-plan-terms.js +11 -13
  35. package/dist/cjs/components/plan-type-selector/hooks/use-max-discounts-for-plan-terms.js.map +1 -1
  36. package/dist/cjs/components/plans-2023-tooltip.js +16 -5
  37. package/dist/cjs/components/plans-2023-tooltip.js.map +1 -1
  38. package/dist/cjs/components/shared/action-button/index.js +22 -7
  39. package/dist/cjs/components/shared/action-button/index.js.map +1 -1
  40. package/dist/cjs/components/shared/action-button/style.scss +4 -0
  41. package/dist/cjs/components/shared/billing-timeframe/index.js +8 -4
  42. package/dist/cjs/components/shared/billing-timeframe/index.js.map +1 -1
  43. package/dist/cjs/components/shared/header-price/index.js +60 -15
  44. package/dist/cjs/components/shared/header-price/index.js.map +1 -1
  45. package/dist/cjs/components/shared/header-price/style.scss +9 -3
  46. package/dist/cjs/components/shared/storage/components/plan-storage.js +2 -2
  47. package/dist/cjs/components/shared/storage/components/plan-storage.js.map +1 -1
  48. package/dist/cjs/components/shared/storage/components/storage-dropdown.js +29 -6
  49. package/dist/cjs/components/shared/storage/components/storage-dropdown.js.map +1 -1
  50. package/dist/cjs/components/shared/storage/components/storage-feature-label.js +2 -1
  51. package/dist/cjs/components/shared/storage/components/storage-feature-label.js.map +1 -1
  52. package/dist/cjs/components/shared/storage/hooks/use-plan-storage.js +2 -0
  53. package/dist/cjs/components/shared/storage/hooks/use-plan-storage.js.map +1 -1
  54. package/dist/cjs/fixtures/sites-purchases.js +2 -4
  55. package/dist/cjs/fixtures/sites-purchases.js.map +1 -1
  56. package/dist/cjs/grid-context.js +4 -1
  57. package/dist/cjs/grid-context.js.map +1 -1
  58. package/dist/cjs/hooks/data-store/get-renewal-pricing-text.js +50 -0
  59. package/dist/cjs/hooks/data-store/get-renewal-pricing-text.js.map +1 -0
  60. package/dist/cjs/hooks/data-store/use-grid-plans-for-comparison-grid.js +6 -1
  61. package/dist/cjs/hooks/data-store/use-grid-plans-for-comparison-grid.js.map +1 -1
  62. package/dist/cjs/hooks/data-store/use-grid-plans-for-features-grid.js +6 -1
  63. package/dist/cjs/hooks/data-store/use-grid-plans-for-features-grid.js.map +1 -1
  64. package/dist/cjs/hooks/data-store/use-grid-plans.js +175 -21
  65. package/dist/cjs/hooks/data-store/use-grid-plans.js.map +1 -1
  66. package/dist/cjs/hooks/data-store/use-highlight-labels.js +13 -4
  67. package/dist/cjs/hooks/data-store/use-highlight-labels.js.map +1 -1
  68. package/dist/cjs/hooks/data-store/use-plan-billing-description.js +68 -13
  69. package/dist/cjs/hooks/data-store/use-plan-billing-description.js.map +1 -1
  70. package/dist/cjs/hooks/data-store/use-plan-features-for-grid-plans.js +76 -2
  71. package/dist/cjs/hooks/data-store/use-plan-features-for-grid-plans.js.map +1 -1
  72. package/dist/cjs/hooks/data-store/use-restructured-plan-features-for-comparison-grid.js +60 -12
  73. package/dist/cjs/hooks/data-store/use-restructured-plan-features-for-comparison-grid.js.map +1 -1
  74. package/dist/cjs/hooks/data-store/use-title-badges.js +19 -0
  75. package/dist/cjs/hooks/data-store/use-title-badges.js.map +1 -0
  76. package/dist/cjs/hooks/use-is-large-currency.js +2 -2
  77. package/dist/cjs/hooks/use-is-large-currency.js.map +1 -1
  78. package/dist/cjs/hooks/use-visible-grid-plans.js +70 -0
  79. package/dist/cjs/hooks/use-visible-grid-plans.js.map +1 -0
  80. package/dist/cjs/index.js +6 -1
  81. package/dist/cjs/index.js.map +1 -1
  82. package/dist/cjs/lib/get-plan-features-object.js +15 -2
  83. package/dist/cjs/lib/get-plan-features-object.js.map +1 -1
  84. package/dist/cjs/lib/plan-pricing-utils.js +135 -0
  85. package/dist/cjs/lib/plan-pricing-utils.js.map +1 -0
  86. package/dist/esm/_shared.scss +4 -3
  87. package/dist/esm/components/comparison-grid/index.js +100 -93
  88. package/dist/esm/components/comparison-grid/index.js.map +1 -1
  89. package/dist/esm/components/comparison-grid/style.scss +10 -2
  90. package/dist/esm/components/features-grid/client-logo-list/client-list.js +0 -12
  91. package/dist/esm/components/features-grid/client-logo-list/client-list.js.map +1 -1
  92. package/dist/esm/components/features-grid/index.js +9 -6
  93. package/dist/esm/components/features-grid/index.js.map +1 -1
  94. package/dist/esm/components/features-grid/plan-features-list.js +10 -3
  95. package/dist/esm/components/features-grid/plan-features-list.js.map +1 -1
  96. package/dist/esm/components/features-grid/plan-headers.js +3 -3
  97. package/dist/esm/components/features-grid/plan-headers.js.map +1 -1
  98. package/dist/esm/components/features-grid/plan-tagline.js +1 -1
  99. package/dist/esm/components/features-grid/plan-tagline.js.map +1 -1
  100. package/dist/esm/components/features-grid/style.scss +107 -19
  101. package/dist/esm/components/features-grid/table.js +1 -1
  102. package/dist/esm/components/features-grid/table.js.map +1 -1
  103. package/dist/esm/components/features.js +44 -5
  104. package/dist/esm/components/features.js.map +1 -1
  105. package/dist/esm/components/item.js +1 -1
  106. package/dist/esm/components/item.js.map +1 -1
  107. package/dist/esm/components/plan-button/index.js +5 -3
  108. package/dist/esm/components/plan-button/index.js.map +1 -1
  109. package/dist/esm/components/plan-button/style.scss +75 -51
  110. package/dist/esm/components/plan-div-td-container.js +4 -1
  111. package/dist/esm/components/plan-div-td-container.js.map +1 -1
  112. package/dist/esm/components/plan-logo.js +7 -4
  113. package/dist/esm/components/plan-logo.js.map +1 -1
  114. package/dist/esm/components/plan-type-selector/components/interval-type-dropdown.js +12 -1
  115. package/dist/esm/components/plan-type-selector/components/interval-type-dropdown.js.map +1 -1
  116. package/dist/esm/components/plan-type-selector/hooks/use-max-discount.js +3 -33
  117. package/dist/esm/components/plan-type-selector/hooks/use-max-discount.js.map +1 -1
  118. package/dist/esm/components/plan-type-selector/hooks/use-max-discounts-for-plan-terms.js +11 -13
  119. package/dist/esm/components/plan-type-selector/hooks/use-max-discounts-for-plan-terms.js.map +1 -1
  120. package/dist/esm/components/plans-2023-tooltip.js +16 -5
  121. package/dist/esm/components/plans-2023-tooltip.js.map +1 -1
  122. package/dist/esm/components/shared/action-button/index.js +22 -7
  123. package/dist/esm/components/shared/action-button/index.js.map +1 -1
  124. package/dist/esm/components/shared/action-button/style.scss +4 -0
  125. package/dist/esm/components/shared/billing-timeframe/index.js +8 -4
  126. package/dist/esm/components/shared/billing-timeframe/index.js.map +1 -1
  127. package/dist/esm/components/shared/header-price/index.js +60 -15
  128. package/dist/esm/components/shared/header-price/index.js.map +1 -1
  129. package/dist/esm/components/shared/header-price/style.scss +9 -3
  130. package/dist/esm/components/shared/storage/components/plan-storage.js +2 -2
  131. package/dist/esm/components/shared/storage/components/plan-storage.js.map +1 -1
  132. package/dist/esm/components/shared/storage/components/storage-dropdown.js +30 -7
  133. package/dist/esm/components/shared/storage/components/storage-dropdown.js.map +1 -1
  134. package/dist/esm/components/shared/storage/components/storage-feature-label.js +2 -1
  135. package/dist/esm/components/shared/storage/components/storage-feature-label.js.map +1 -1
  136. package/dist/esm/components/shared/storage/hooks/use-plan-storage.js +3 -1
  137. package/dist/esm/components/shared/storage/hooks/use-plan-storage.js.map +1 -1
  138. package/dist/esm/fixtures/sites-purchases.js +2 -4
  139. package/dist/esm/fixtures/sites-purchases.js.map +1 -1
  140. package/dist/esm/grid-context.js +4 -1
  141. package/dist/esm/grid-context.js.map +1 -1
  142. package/dist/esm/hooks/data-store/get-renewal-pricing-text.js +47 -0
  143. package/dist/esm/hooks/data-store/get-renewal-pricing-text.js.map +1 -0
  144. package/dist/esm/hooks/data-store/use-grid-plans-for-comparison-grid.js +6 -1
  145. package/dist/esm/hooks/data-store/use-grid-plans-for-comparison-grid.js.map +1 -1
  146. package/dist/esm/hooks/data-store/use-grid-plans-for-features-grid.js +6 -1
  147. package/dist/esm/hooks/data-store/use-grid-plans-for-features-grid.js.map +1 -1
  148. package/dist/esm/hooks/data-store/use-grid-plans.js +176 -22
  149. package/dist/esm/hooks/data-store/use-grid-plans.js.map +1 -1
  150. package/dist/esm/hooks/data-store/use-highlight-labels.js +14 -5
  151. package/dist/esm/hooks/data-store/use-highlight-labels.js.map +1 -1
  152. package/dist/esm/hooks/data-store/use-plan-billing-description.js +66 -11
  153. package/dist/esm/hooks/data-store/use-plan-billing-description.js.map +1 -1
  154. package/dist/esm/hooks/data-store/use-plan-features-for-grid-plans.js +77 -3
  155. package/dist/esm/hooks/data-store/use-plan-features-for-grid-plans.js.map +1 -1
  156. package/dist/esm/hooks/data-store/use-restructured-plan-features-for-comparison-grid.js +59 -11
  157. package/dist/esm/hooks/data-store/use-restructured-plan-features-for-comparison-grid.js.map +1 -1
  158. package/dist/esm/hooks/data-store/use-title-badges.js +17 -0
  159. package/dist/esm/hooks/data-store/use-title-badges.js.map +1 -0
  160. package/dist/esm/hooks/use-is-large-currency.js +1 -1
  161. package/dist/esm/hooks/use-is-large-currency.js.map +1 -1
  162. package/dist/esm/hooks/use-visible-grid-plans.js +66 -0
  163. package/dist/esm/hooks/use-visible-grid-plans.js.map +1 -0
  164. package/dist/esm/index.js +2 -0
  165. package/dist/esm/index.js.map +1 -1
  166. package/dist/esm/lib/get-plan-features-object.js +15 -2
  167. package/dist/esm/lib/get-plan-features-object.js.map +1 -1
  168. package/dist/esm/lib/plan-pricing-utils.js +129 -0
  169. package/dist/esm/lib/plan-pricing-utils.js.map +1 -0
  170. package/dist/tsconfig-cjs.tsbuildinfo +1 -1
  171. package/dist/tsconfig.tsbuildinfo +1 -1
  172. package/dist/types/components/comparison-grid/index.d.ts +1 -1
  173. package/dist/types/components/comparison-grid/index.d.ts.map +1 -1
  174. package/dist/types/components/features-grid/client-logo-list/client-list.d.ts.map +1 -1
  175. package/dist/types/components/features-grid/index.d.ts.map +1 -1
  176. package/dist/types/components/features-grid/plan-features-list.d.ts.map +1 -1
  177. package/dist/types/components/features-grid/plan-headers.d.ts +2 -0
  178. package/dist/types/components/features-grid/plan-headers.d.ts.map +1 -1
  179. package/dist/types/components/features-grid/table.d.ts.map +1 -1
  180. package/dist/types/components/features.d.ts.map +1 -1
  181. package/dist/types/components/item.d.ts +2 -1
  182. package/dist/types/components/item.d.ts.map +1 -1
  183. package/dist/types/components/plan-button/index.d.ts +2 -1
  184. package/dist/types/components/plan-button/index.d.ts.map +1 -1
  185. package/dist/types/components/plan-div-td-container.d.ts +2 -0
  186. package/dist/types/components/plan-div-td-container.d.ts.map +1 -1
  187. package/dist/types/components/plan-logo.d.ts.map +1 -1
  188. package/dist/types/components/plan-type-selector/components/interval-type-dropdown.d.ts.map +1 -1
  189. package/dist/types/components/plan-type-selector/hooks/use-max-discount.d.ts.map +1 -1
  190. package/dist/types/components/plan-type-selector/hooks/use-max-discounts-for-plan-terms.d.ts.map +1 -1
  191. package/dist/types/components/plans-2023-tooltip.d.ts.map +1 -1
  192. package/dist/types/components/shared/action-button/index.d.ts +2 -1
  193. package/dist/types/components/shared/action-button/index.d.ts.map +1 -1
  194. package/dist/types/components/shared/billing-timeframe/index.d.ts.map +1 -1
  195. package/dist/types/components/shared/header-price/index.d.ts.map +1 -1
  196. package/dist/types/components/shared/storage/components/storage-dropdown.d.ts.map +1 -1
  197. package/dist/types/components/shared/storage/components/storage-feature-label.d.ts.map +1 -1
  198. package/dist/types/components/shared/storage/hooks/use-plan-storage.d.ts +1 -1
  199. package/dist/types/components/shared/storage/hooks/use-plan-storage.d.ts.map +1 -1
  200. package/dist/types/fixtures/sites-purchases.d.ts +2 -4
  201. package/dist/types/fixtures/sites-purchases.d.ts.map +1 -1
  202. package/dist/types/grid-context.d.ts +4 -1
  203. package/dist/types/grid-context.d.ts.map +1 -1
  204. package/dist/types/hooks/data-store/get-renewal-pricing-text.d.ts +14 -0
  205. package/dist/types/hooks/data-store/get-renewal-pricing-text.d.ts.map +1 -0
  206. package/dist/types/hooks/data-store/types.d.ts +21 -0
  207. package/dist/types/hooks/data-store/types.d.ts.map +1 -1
  208. package/dist/types/hooks/data-store/use-grid-plans-for-comparison-grid.d.ts +1 -1
  209. package/dist/types/hooks/data-store/use-grid-plans-for-comparison-grid.d.ts.map +1 -1
  210. package/dist/types/hooks/data-store/use-grid-plans-for-features-grid.d.ts +1 -1
  211. package/dist/types/hooks/data-store/use-grid-plans-for-features-grid.d.ts.map +1 -1
  212. package/dist/types/hooks/data-store/use-grid-plans.d.ts.map +1 -1
  213. package/dist/types/hooks/data-store/use-highlight-labels.d.ts.map +1 -1
  214. package/dist/types/hooks/data-store/use-plan-billing-description.d.ts.map +1 -1
  215. package/dist/types/hooks/data-store/use-plan-features-for-grid-plans.d.ts +4 -1
  216. package/dist/types/hooks/data-store/use-plan-features-for-grid-plans.d.ts.map +1 -1
  217. package/dist/types/hooks/data-store/use-restructured-plan-features-for-comparison-grid.d.ts +4 -1
  218. package/dist/types/hooks/data-store/use-restructured-plan-features-for-comparison-grid.d.ts.map +1 -1
  219. package/dist/types/hooks/data-store/use-title-badges.d.ts +9 -0
  220. package/dist/types/hooks/data-store/use-title-badges.d.ts.map +1 -0
  221. package/dist/types/hooks/use-visible-grid-plans.d.ts +14 -0
  222. package/dist/types/hooks/use-visible-grid-plans.d.ts.map +1 -0
  223. package/dist/types/index.d.ts +7 -0
  224. package/dist/types/index.d.ts.map +1 -1
  225. package/dist/types/lib/get-plan-features-object.d.ts +1 -1
  226. package/dist/types/lib/get-plan-features-object.d.ts.map +1 -1
  227. package/dist/types/lib/plan-pricing-utils.d.ts +105 -0
  228. package/dist/types/lib/plan-pricing-utils.d.ts.map +1 -0
  229. package/dist/types/types.d.ts +29 -2
  230. package/dist/types/types.d.ts.map +1 -1
  231. package/package.json +38 -28
  232. package/src/_shared.scss +4 -3
  233. package/src/components/comparison-grid/index.tsx +258 -181
  234. package/src/components/comparison-grid/style.scss +10 -2
  235. package/src/components/features-grid/client-logo-list/client-list.tsx +0 -25
  236. package/src/components/features-grid/index.tsx +35 -18
  237. package/src/components/features-grid/plan-features-list.tsx +15 -4
  238. package/src/components/features-grid/plan-headers.tsx +10 -3
  239. package/src/components/features-grid/plan-tagline.tsx +1 -1
  240. package/src/components/features-grid/style.scss +107 -19
  241. package/src/components/features-grid/table.tsx +4 -2
  242. package/src/components/features.tsx +66 -6
  243. package/src/components/item.tsx +6 -3
  244. package/src/components/plan-button/index.tsx +7 -1
  245. package/src/components/plan-button/style.scss +75 -51
  246. package/src/components/plan-div-td-container.tsx +6 -2
  247. package/src/components/plan-logo.tsx +16 -9
  248. package/src/components/plan-type-selector/components/interval-type-dropdown.tsx +14 -1
  249. package/src/components/plan-type-selector/hooks/use-max-discount.ts +8 -47
  250. package/src/components/plan-type-selector/hooks/use-max-discounts-for-plan-terms.ts +19 -17
  251. package/src/components/plans-2023-tooltip.tsx +17 -5
  252. package/src/components/shared/action-button/index.tsx +46 -5
  253. package/src/components/shared/action-button/style.scss +4 -0
  254. package/src/components/shared/billing-timeframe/index.tsx +12 -7
  255. package/src/components/shared/header-price/index.tsx +129 -27
  256. package/src/components/shared/header-price/style.scss +9 -3
  257. package/src/components/shared/storage/components/plan-storage.tsx +2 -2
  258. package/src/components/shared/storage/components/storage-dropdown.tsx +36 -15
  259. package/src/components/shared/storage/components/storage-feature-label.tsx +2 -1
  260. package/src/components/shared/storage/hooks/use-plan-storage.ts +3 -0
  261. package/src/components/test/actions-button.tsx +5 -0
  262. package/src/components/test/billing-timeframe.tsx +1 -1
  263. package/src/components/test/header-price.tsx +342 -4
  264. package/src/fixtures/sites-purchases.ts +2 -4
  265. package/src/grid-context.tsx +9 -0
  266. package/src/hooks/data-store/get-renewal-pricing-text.ts +73 -0
  267. package/src/hooks/data-store/types.ts +21 -0
  268. package/src/hooks/data-store/use-grid-plans-for-comparison-grid.ts +10 -0
  269. package/src/hooks/data-store/use-grid-plans-for-features-grid.ts +10 -0
  270. package/src/hooks/data-store/use-grid-plans.tsx +189 -23
  271. package/src/hooks/data-store/use-highlight-labels.ts +12 -3
  272. package/src/hooks/data-store/use-plan-billing-description.tsx +80 -15
  273. package/src/hooks/data-store/use-plan-features-for-grid-plans.ts +135 -1
  274. package/src/hooks/data-store/use-restructured-plan-features-for-comparison-grid.ts +93 -20
  275. package/src/hooks/data-store/use-title-badges.ts +31 -0
  276. package/src/hooks/test/use-visible-grid-plans.tsx +116 -0
  277. package/src/hooks/use-is-large-currency.ts +1 -1
  278. package/src/hooks/use-visible-grid-plans.tsx +102 -0
  279. package/src/index.tsx +18 -0
  280. package/src/lib/get-plan-features-object.ts +23 -2
  281. package/src/lib/plan-pricing-utils.ts +211 -0
  282. package/src/lib/test/plan-pricing-utils.ts +594 -0
  283. package/src/style-imports.d.ts +3 -0
  284. package/src/types.ts +41 -0
  285. package/dist/cjs/components/features-grid/mobile-free-domain.js +0 -25
  286. package/dist/cjs/components/features-grid/mobile-free-domain.js.map +0 -1
  287. package/dist/cjs/lib/get-plan-pricing-info-from-grid-plans.js +0 -15
  288. package/dist/cjs/lib/get-plan-pricing-info-from-grid-plans.js.map +0 -1
  289. package/dist/esm/components/features-grid/mobile-free-domain.js +0 -23
  290. package/dist/esm/components/features-grid/mobile-free-domain.js.map +0 -1
  291. package/dist/esm/lib/get-plan-pricing-info-from-grid-plans.js +0 -12
  292. package/dist/esm/lib/get-plan-pricing-info-from-grid-plans.js.map +0 -1
  293. package/dist/types/components/features-grid/mobile-free-domain.d.ts +0 -8
  294. package/dist/types/components/features-grid/mobile-free-domain.d.ts.map +0 -1
  295. package/dist/types/lib/get-plan-pricing-info-from-grid-plans.d.ts +0 -9
  296. package/dist/types/lib/get-plan-pricing-info-from-grid-plans.d.ts.map +0 -1
  297. package/src/components/features-grid/mobile-free-domain.tsx +0 -51
  298. package/src/lib/get-plan-pricing-info-from-grid-plans.ts +0 -31
@@ -0,0 +1,211 @@
1
+ import { Plans } from '@automattic/data-stores';
2
+
3
+ /**
4
+ * Intermediate structure that normalizes plan pricing from different sources
5
+ * (PricingMetaForGridPlan, WPCOMProductVariant) into a common shape suitable
6
+ * for discount calculations.
7
+ *
8
+ * All prices are in the smallest currency unit (e.g. cents).
9
+ */
10
+ export interface PlanPriceInfo {
11
+ /** Billing period length in months (1, 12, 24, 36) */
12
+ termMonths: number;
13
+ /** Regular price per month — no intro offer, no site-level discount */
14
+ regularPricePerMonth: number;
15
+ /** Site-level discounted price per month (currency conversion / proration credit), if any */
16
+ discountedPricePerMonth?: number;
17
+ introOffer?: {
18
+ /** Monthly-equivalent price during the intro period */
19
+ pricePerMonth: number;
20
+ /** How many months the intro offer lasts */
21
+ durationMonths: number;
22
+ /** False once the offer has already been used by this user */
23
+ isActive: boolean;
24
+ };
25
+ }
26
+
27
+ // billingPeriod values are in days (from PERIOD_LIST in calypso-products)
28
+ const BILLING_PERIOD_DAYS_TO_MONTHS: Record< number, number > = {
29
+ 31: 1,
30
+ 365: 12,
31
+ 730: 24,
32
+ 1095: 36,
33
+ };
34
+
35
+ function billingPeriodDaysToMonths( billingPeriod: number | undefined ): number | null {
36
+ if ( billingPeriod === undefined || billingPeriod === null ) {
37
+ return null;
38
+ }
39
+ return BILLING_PERIOD_DAYS_TO_MONTHS[ billingPeriod ] ?? null;
40
+ }
41
+
42
+ /**
43
+ * Calculates the total cost of a plan over `durationMonths` months.
44
+ *
45
+ * Handles partial billing periods via pro-ration (e.g. a yearly plan priced at
46
+ * $200/year costs $100 for 6 months).
47
+ *
48
+ * When `useIntroOffer` is true (default) and an active intro offer exists, the
49
+ * intro price is applied for the first `introOffer.durationMonths` months, and
50
+ * the regular (or discounted) price for the remainder.
51
+ *
52
+ * @example
53
+ * // Monthly plan: $10 intro for 1 month, then $20/month; cost for 6 months:
54
+ * // 1×$10 + 5×$20 = $110 (in cents: 1100 + 10000 = 11000)
55
+ * getPlanPriceForDuration( info, 6, { useIntroOffer: true } ) // 11000
56
+ *
57
+ * @example
58
+ * // Yearly plan: $100/yr intro, $200/yr regular; cost for 6 months:
59
+ * // useIntroOffer=true → 6×($100/12) = $50 (5000 cents)
60
+ * // useIntroOffer=false → 6×($200/12) = $100 (10000 cents)
61
+ */
62
+ export function getPlanPriceForDuration(
63
+ info: PlanPriceInfo,
64
+ durationMonths: number,
65
+ { useIntroOffer = true }: { useIntroOffer?: boolean } = {}
66
+ ): number {
67
+ const basePricePerMonth = info.discountedPricePerMonth ?? info.regularPricePerMonth;
68
+
69
+ if ( useIntroOffer && info.introOffer?.isActive ) {
70
+ const introMonths = Math.min( durationMonths, info.introOffer.durationMonths );
71
+ const regularMonths = Math.max( 0, durationMonths - info.introOffer.durationMonths );
72
+ return introMonths * info.introOffer.pricePerMonth + regularMonths * basePricePerMonth;
73
+ }
74
+
75
+ return durationMonths * basePricePerMonth;
76
+ }
77
+
78
+ /**
79
+ * Calculates the percentage discount between a reference price and a cheaper price.
80
+ *
81
+ * Always uses Math.floor — conservative, never overstates savings.
82
+ *
83
+ * Returns `undefined` (not 0) when there is no saving, allowing callers to
84
+ * distinguish "no discount" from a computed "0% discount".
85
+ */
86
+ export function calculateDiscountPercentage(
87
+ referencePrice: number,
88
+ cheaperPrice: number
89
+ ): number | undefined {
90
+ if ( cheaperPrice >= referencePrice || referencePrice <= 0 ) {
91
+ return undefined;
92
+ }
93
+ return Math.floor( ( 100 * ( referencePrice - cheaperPrice ) ) / referencePrice );
94
+ }
95
+
96
+ /**
97
+ * Converts a `PricingMetaForGridPlan` (from @automattic/data-stores Plans hooks)
98
+ * into a `PlanPriceInfo` suitable for use with `getPlanPriceForDuration` and
99
+ * `calculateDiscountPercentage`.
100
+ *
101
+ * Returns null when required pricing data is absent (e.g. free/enterprise plans
102
+ * that have no monthly price, or plans whose billing period is unknown).
103
+ */
104
+ export function fromPricingMetaForGridPlan(
105
+ meta: Plans.PricingMetaForGridPlan
106
+ ): PlanPriceInfo | null {
107
+ const termMonths = billingPeriodDaysToMonths( meta.billingPeriod );
108
+ if ( termMonths === null || meta.originalPrice.monthly === null ) {
109
+ return null;
110
+ }
111
+
112
+ const isIntroActive = !! meta.introOffer && ! meta.introOffer.isOfferComplete;
113
+
114
+ return {
115
+ termMonths,
116
+ regularPricePerMonth: meta.originalPrice.monthly,
117
+ // The API sometimes sets discountedPrice.monthly to the intro offer price rather than
118
+ // a genuine site-level discount (e.g. currency conversion, proration credit). When an
119
+ // intro offer is active the intro structure already captures the discounted period, so
120
+ // using discountedPrice here would contaminate the post-intro "regular" rate used by
121
+ // getPlanPriceForDuration — producing incorrect totals for the non-intro months.
122
+ discountedPricePerMonth: isIntroActive ? undefined : meta.discountedPrice.monthly ?? undefined,
123
+ introOffer: meta.introOffer
124
+ ? {
125
+ pricePerMonth: meta.introOffer.rawPrice.monthly,
126
+ durationMonths:
127
+ meta.introOffer.intervalCount * ( meta.introOffer.intervalUnit === 'year' ? 12 : 1 ),
128
+ isActive: isIntroActive,
129
+ }
130
+ : undefined,
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Minimal structural type describing the pricing fields needed from a product
136
+ * variant. `WPCOMProductVariant` (defined in checkout) satisfies this interface
137
+ * structurally, so it can be passed directly without importing the checkout type
138
+ * into this package.
139
+ *
140
+ * Key invariants:
141
+ * - `priceBeforeDiscounts` is always the full-term cost at the regular rate.
142
+ * - `priceInteger` is the actual amount charged, which embeds both intro and
143
+ * non-intro portions when an intro offer spans only part of the term
144
+ * (e.g. a 1-year intro on a 2-year plan).
145
+ * - When `priceInteger < priceBeforeDiscounts`, the intro price portion is
146
+ * derived by subtracting the non-intro months at the regular rate.
147
+ */
148
+ export interface VariantPriceData {
149
+ introductoryInterval: number;
150
+ /** 'month' | 'year' */
151
+ introductoryTerm: string;
152
+ /** Full-term price at the regular (non-intro) rate */
153
+ priceBeforeDiscounts: number;
154
+ /** Actual price charged for the full term (may embed an intro offer) */
155
+ priceInteger: number;
156
+ termIntervalInMonths: number;
157
+ }
158
+
159
+ /**
160
+ * Converts a variant price data object (structurally compatible with
161
+ * `WPCOMProductVariant`) into a `PlanPriceInfo`.
162
+ *
163
+ * `discountedPricePerMonth` is intentionally not set: `WPCOMProductVariant`
164
+ * does not separate site-level discounts from the intro price. Coupon discounts
165
+ * in checkout are tracked separately via `product.coupon_savings_integer`.
166
+ *
167
+ * Per-month values are derived by dividing full-term prices (integers in the
168
+ * smallest currency unit) by the number of months. The result is rounded to the
169
+ * nearest integer (Math.round) so that all fields in the returned `PlanPriceInfo`
170
+ * remain whole-cent values safe for use with currency formatters. The rounding
171
+ * error is at most 0.5¢ per month and is negligible for percentage comparisons.
172
+ */
173
+ export function fromVariantPriceData( variant: VariantPriceData ): PlanPriceInfo {
174
+ const {
175
+ termIntervalInMonths: termMonths,
176
+ priceBeforeDiscounts,
177
+ priceInteger,
178
+ introductoryInterval,
179
+ introductoryTerm,
180
+ } = variant;
181
+
182
+ const regularPricePerMonth = Math.round( priceBeforeDiscounts / termMonths );
183
+
184
+ const introDurationMonths =
185
+ introductoryInterval > 0 ? introductoryInterval * ( introductoryTerm === 'year' ? 12 : 1 ) : 0;
186
+
187
+ let introOffer: PlanPriceInfo[ 'introOffer' ] | undefined;
188
+
189
+ if ( introDurationMonths > 0 && priceInteger < priceBeforeDiscounts ) {
190
+ // When the intro spans the full term (introDurationMonths >= termMonths), all of
191
+ // priceInteger is at the intro rate and there are zero non-intro months.
192
+ // When the intro is shorter than the term (introDurationMonths < termMonths), we
193
+ // subtract the non-intro portion (billed at the regular rate) to isolate the intro cost.
194
+ const nonIntroMonths = Math.max( 0, termMonths - introDurationMonths );
195
+ const introPriceTotal = priceInteger - nonIntroMonths * regularPricePerMonth;
196
+
197
+ // A non-positive introPriceTotal means inconsistent data (e.g. a sub-term intro
198
+ // with a priceInteger that is less than the non-intro months at the regular rate).
199
+ if ( introPriceTotal > 0 ) {
200
+ // When introDurationMonths > termMonths the whole billing term is within the
201
+ // intro period, so we spread introPriceTotal over termMonths (not introDurationMonths).
202
+ introOffer = {
203
+ pricePerMonth: Math.round( introPriceTotal / Math.min( introDurationMonths, termMonths ) ),
204
+ durationMonths: introDurationMonths,
205
+ isActive: true,
206
+ };
207
+ }
208
+ }
209
+
210
+ return { termMonths, regularPricePerMonth, introOffer };
211
+ }