@wix/himalaya-cli 0.1583.0 → 0.1585.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.
Files changed (313) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/app-module.d.ts +7 -4
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/bindings.d.ts +7 -6
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/blog-v3/src/extensions.d.ts +2 -0
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/comments-v1/src/extensions.d.ts +2 -0
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/events/src/index.d.ts +3 -0
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/groups/src/extensions.d.ts +15 -0
  7. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/loyalty/src/extensions.d.ts +13 -0
  8. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/members-v1/src/index.d.ts +19 -4
  9. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +2 -2
  10. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdui.mjs +8 -2
  11. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-comments.mjs +3 -0
  12. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-events.mjs +4 -2
  13. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-groups.mjs +5 -0
  14. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-loyalty.mjs +4 -0
  15. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-members.mjs +10 -3
  16. package/dist/cli.mjs +2201 -1716
  17. package/dist/preview-assets.generated.bin +0 -0
  18. package/fixtures/wix-canned.json +25 -0
  19. package/guides/branded-lite-site-binding.md +7 -1
  20. package/guides/designing-himi-screens.md +47 -7
  21. package/guides/himi-skill.md +16 -0
  22. package/guides/index.json +1 -0
  23. package/guides/one-app-to-himi-app.md +52 -1
  24. package/guides/sdk-reference.json +37 -9
  25. package/guides/tier-5-add-a-screen.md +9 -0
  26. package/guides/wix-api-usage.md +9 -0
  27. package/package.json +2 -2
  28. package/templates/beauty-salon/config.ts +7 -0
  29. package/templates/beauty-salon/dev/index.ts +758 -352
  30. package/templates/beauty-salon/himalaya.content.json +3 -0
  31. package/templates/beauty-salon/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  32. package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +19 -3
  33. package/templates/beauty-salon/tier5-src/_shared/himi-restaurants.ts +2 -1
  34. package/templates/beauty-salon/tier5-src/_shared/owner-editor-value.ts +164 -0
  35. package/templates/beauty-salon/tier5-src/_shared/store-product-info.ts +50 -0
  36. package/templates/beauty-salon/tier5-src/himi-blog-post/index.ts +21 -8
  37. package/templates/beauty-salon/tier5-src/himi-branded-lite-category/index.ts +3 -2
  38. package/templates/beauty-salon/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  39. package/templates/beauty-salon/tier5-src/himi-branded-lite-plans/index.ts +2 -1
  40. package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +14 -3
  41. package/templates/beauty-salon/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
  42. package/templates/beauty-salon/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  43. package/templates/beauty-salon-owner/dev/index.ts +129 -66
  44. package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +19 -3
  45. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  46. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
  47. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  48. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
  49. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
  50. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
  51. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
  52. package/templates/branded-lite/config.ts +9 -0
  53. package/templates/branded-lite/dev/index.ts +4988 -3304
  54. package/templates/branded-lite/himalaya.content.json +3 -0
  55. package/templates/branded-lite/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  56. package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +19 -3
  57. package/templates/branded-lite/tier5-src/_shared/event-registration.ts +86 -0
  58. package/templates/branded-lite/tier5-src/_shared/himi-restaurants.ts +2 -1
  59. package/templates/branded-lite/tier5-src/_shared/owner-editor-value.ts +164 -0
  60. package/templates/branded-lite/tier5-src/_shared/store-product-info.ts +50 -0
  61. package/templates/branded-lite/tier5-src/himi-blog-post/index.ts +21 -8
  62. package/templates/branded-lite/tier5-src/himi-branded-lite-badges/index.ts +2 -1
  63. package/templates/branded-lite/tier5-src/himi-branded-lite-category/index.ts +3 -2
  64. package/templates/branded-lite/tier5-src/himi-branded-lite-feed/index.ts +4 -3
  65. package/templates/branded-lite/tier5-src/himi-branded-lite-group-about/index.ts +243 -0
  66. package/templates/branded-lite/tier5-src/himi-branded-lite-loyalty/index.ts +115 -49
  67. package/templates/branded-lite/tier5-src/himi-branded-lite-plans/index.ts +2 -1
  68. package/templates/branded-lite/tier5-src/himi-branded-lite-podcasts/index.ts +2 -1
  69. package/templates/branded-lite/tier5-src/himi-branded-lite-portfolio/index.ts +2 -1
  70. package/templates/branded-lite/tier5-src/himi-branded-lite-portfolio-project/index.ts +3 -2
  71. package/templates/branded-lite/tier5-src/himi-branded-lite-program/facts.ts +52 -0
  72. package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +21 -11
  73. package/templates/branded-lite/tier5-src/himi-branded-lite-video/index.ts +2 -1
  74. package/templates/branded-lite/tier5-src/himi-events-detail/index.ts +12 -86
  75. package/templates/branded-lite/tier5-src/himi-events-list/index.ts +47 -6
  76. package/templates/branded-lite/tier5-src/himi-store-product/index.ts +13 -3
  77. package/templates/branded-lite/tier5-src/wix-spaces-forum-post/index.ts +3 -2
  78. package/templates/branded-lite/tier5-src/wix-spaces-member-detail/index.ts +53 -37
  79. package/templates/branded-lite/tier5-src/wix-spaces-members/index.ts +100 -34
  80. package/templates/branded-lite/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
  81. package/templates/branded-lite/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  82. package/templates/community-club/config.ts +16 -0
  83. package/templates/community-club/dev/index.ts +1930 -401
  84. package/templates/community-club/himalaya.content.json +6 -0
  85. package/templates/community-club/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  86. package/templates/community-club/tier5-src/_shared/collection-contract.ts +19 -3
  87. package/templates/community-club/tier5-src/_shared/event-registration.ts +86 -0
  88. package/templates/community-club/tier5-src/_shared/himi-restaurants.ts +2 -1
  89. package/templates/community-club/tier5-src/himi-blog-post/index.ts +21 -8
  90. package/templates/community-club/tier5-src/himi-branded-lite-feed/index.ts +4 -3
  91. package/templates/community-club/tier5-src/himi-branded-lite-group-about/index.ts +243 -0
  92. package/templates/community-club/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  93. package/templates/community-club/tier5-src/himi-events-detail/index.ts +12 -86
  94. package/templates/community-club/tier5-src/himi-events-list/index.ts +47 -6
  95. package/templates/community-club/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  96. package/templates/community-club-owner/dev/index.ts +53 -1
  97. package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +19 -3
  98. package/templates/community-club-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
  99. package/templates/community-club-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  100. package/templates/community-club-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
  101. package/templates/community-club-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  102. package/templates/community-club-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
  103. package/templates/community-club-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  104. package/templates/community-club-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
  105. package/templates/events-meetups/config.ts +16 -0
  106. package/templates/events-meetups/dev/index.ts +1930 -401
  107. package/templates/events-meetups/himalaya.content.json +6 -0
  108. package/templates/events-meetups/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  109. package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +19 -3
  110. package/templates/events-meetups/tier5-src/_shared/event-registration.ts +86 -0
  111. package/templates/events-meetups/tier5-src/_shared/himi-restaurants.ts +2 -1
  112. package/templates/events-meetups/tier5-src/himi-blog-post/index.ts +21 -8
  113. package/templates/events-meetups/tier5-src/himi-branded-lite-feed/index.ts +4 -3
  114. package/templates/events-meetups/tier5-src/himi-branded-lite-group-about/index.ts +243 -0
  115. package/templates/events-meetups/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  116. package/templates/events-meetups/tier5-src/himi-events-detail/index.ts +12 -86
  117. package/templates/events-meetups/tier5-src/himi-events-list/index.ts +47 -6
  118. package/templates/events-meetups/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  119. package/templates/events-meetups-owner/dev/index.ts +6 -0
  120. package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +19 -3
  121. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
  122. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  123. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
  124. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  125. package/templates/gym-fitness/config.ts +7 -0
  126. package/templates/gym-fitness/dev/index.ts +1115 -278
  127. package/templates/gym-fitness/himalaya.content.json +3 -0
  128. package/templates/gym-fitness/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  129. package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +19 -3
  130. package/templates/gym-fitness/tier5-src/_shared/event-registration.ts +86 -0
  131. package/templates/gym-fitness/tier5-src/_shared/himi-restaurants.ts +2 -1
  132. package/templates/gym-fitness/tier5-src/_shared/owner-editor-value.ts +164 -0
  133. package/templates/gym-fitness/tier5-src/_shared/store-product-info.ts +50 -0
  134. package/templates/gym-fitness/tier5-src/himi-blog-post/index.ts +21 -8
  135. package/templates/gym-fitness/tier5-src/himi-branded-lite-category/index.ts +3 -2
  136. package/templates/gym-fitness/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  137. package/templates/gym-fitness/tier5-src/himi-branded-lite-plans/index.ts +2 -1
  138. package/templates/gym-fitness/tier5-src/himi-events-detail/index.ts +12 -86
  139. package/templates/gym-fitness/tier5-src/himi-events-list/index.ts +47 -6
  140. package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +14 -3
  141. package/templates/gym-fitness/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
  142. package/templates/gym-fitness/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  143. package/templates/gym-fitness-owner/dev/index.ts +132 -66
  144. package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +19 -3
  145. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
  146. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  147. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
  148. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  149. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
  150. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  151. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
  152. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
  153. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
  154. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
  155. package/templates/nonprofit-cause/config.ts +16 -0
  156. package/templates/nonprofit-cause/dev/index.ts +1925 -396
  157. package/templates/nonprofit-cause/himalaya.content.json +6 -0
  158. package/templates/nonprofit-cause/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  159. package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +19 -3
  160. package/templates/nonprofit-cause/tier5-src/_shared/event-registration.ts +86 -0
  161. package/templates/nonprofit-cause/tier5-src/_shared/himi-restaurants.ts +2 -1
  162. package/templates/nonprofit-cause/tier5-src/himi-blog-post/index.ts +21 -8
  163. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-feed/index.ts +4 -3
  164. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-group-about/index.ts +243 -0
  165. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  166. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-plans/index.ts +2 -1
  167. package/templates/nonprofit-cause/tier5-src/himi-events-detail/index.ts +12 -86
  168. package/templates/nonprofit-cause/tier5-src/himi-events-list/index.ts +47 -6
  169. package/templates/nonprofit-cause/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
  170. package/templates/nonprofit-cause/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  171. package/templates/nonprofit-cause-owner/dev/index.ts +85 -65
  172. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +19 -3
  173. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-rich-guard.ts +27 -0
  174. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
  175. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  176. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
  177. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  178. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
  179. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
  180. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
  181. package/templates/owner-lite/dev/index.ts +132 -66
  182. package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +19 -3
  183. package/templates/owner-lite/tier5-src/himi-owner-lite-event/index.ts +16 -8
  184. package/templates/owner-lite/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  185. package/templates/owner-lite/tier5-src/himi-owner-lite-events/index.ts +4 -2
  186. package/templates/owner-lite/tier5-src/himi-owner-lite-post/index.ts +7 -0
  187. package/templates/owner-lite/tier5-src/himi-owner-lite-product/index.ts +39 -6
  188. package/templates/owner-lite/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  189. package/templates/owner-lite/tier5-src/himi-owner-lite-products/index.ts +7 -4
  190. package/templates/owner-lite/tier5-src/himi-owner-lite-service/index.ts +38 -7
  191. package/templates/owner-lite/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
  192. package/templates/owner-lite/tier5-src/himi-owner-lite-services/index.ts +4 -2
  193. package/templates/podcast-creator/config.ts +7 -0
  194. package/templates/podcast-creator/dev/index.ts +1051 -158
  195. package/templates/podcast-creator/himalaya.content.json +3 -0
  196. package/templates/podcast-creator/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  197. package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +19 -3
  198. package/templates/podcast-creator/tier5-src/_shared/event-registration.ts +86 -0
  199. package/templates/podcast-creator/tier5-src/_shared/himi-restaurants.ts +2 -1
  200. package/templates/podcast-creator/tier5-src/himi-blog-post/index.ts +21 -8
  201. package/templates/podcast-creator/tier5-src/himi-branded-lite-feed/index.ts +4 -3
  202. package/templates/podcast-creator/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  203. package/templates/podcast-creator/tier5-src/himi-branded-lite-plans/index.ts +2 -1
  204. package/templates/podcast-creator/tier5-src/himi-branded-lite-podcasts/index.ts +2 -1
  205. package/templates/podcast-creator/tier5-src/himi-events-detail/index.ts +12 -86
  206. package/templates/podcast-creator/tier5-src/himi-events-list/index.ts +47 -6
  207. package/templates/podcast-creator/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
  208. package/templates/podcast-creator/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  209. package/templates/podcast-creator-owner/dev/index.ts +53 -1
  210. package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +19 -3
  211. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
  212. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  213. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
  214. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  215. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
  216. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  217. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
  218. package/templates/portfolio-creative/config.ts +7 -0
  219. package/templates/portfolio-creative/dev/index.ts +1305 -468
  220. package/templates/portfolio-creative/himalaya.content.json +3 -0
  221. package/templates/portfolio-creative/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  222. package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +19 -3
  223. package/templates/portfolio-creative/tier5-src/_shared/event-registration.ts +86 -0
  224. package/templates/portfolio-creative/tier5-src/_shared/himi-restaurants.ts +2 -1
  225. package/templates/portfolio-creative/tier5-src/_shared/owner-editor-value.ts +164 -0
  226. package/templates/portfolio-creative/tier5-src/_shared/store-product-info.ts +50 -0
  227. package/templates/portfolio-creative/tier5-src/himi-blog-post/index.ts +21 -8
  228. package/templates/portfolio-creative/tier5-src/himi-branded-lite-category/index.ts +3 -2
  229. package/templates/portfolio-creative/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  230. package/templates/portfolio-creative/tier5-src/himi-branded-lite-portfolio/index.ts +2 -1
  231. package/templates/portfolio-creative/tier5-src/himi-branded-lite-portfolio-project/index.ts +3 -2
  232. package/templates/portfolio-creative/tier5-src/himi-events-detail/index.ts +12 -86
  233. package/templates/portfolio-creative/tier5-src/himi-events-list/index.ts +47 -6
  234. package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +14 -3
  235. package/templates/portfolio-creative/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  236. package/templates/portfolio-creative-owner/dev/index.ts +129 -66
  237. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +19 -3
  238. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  239. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
  240. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  241. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
  242. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
  243. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
  244. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
  245. package/templates/restaurant/config.ts +7 -0
  246. package/templates/restaurant/dev/index.ts +1051 -158
  247. package/templates/restaurant/himalaya.content.json +3 -0
  248. package/templates/restaurant/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  249. package/templates/restaurant/tier5-src/_shared/collection-contract.ts +19 -3
  250. package/templates/restaurant/tier5-src/_shared/event-registration.ts +86 -0
  251. package/templates/restaurant/tier5-src/_shared/himi-restaurants.ts +2 -1
  252. package/templates/restaurant/tier5-src/himi-blog-post/index.ts +21 -8
  253. package/templates/restaurant/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  254. package/templates/restaurant/tier5-src/himi-events-detail/index.ts +12 -86
  255. package/templates/restaurant/tier5-src/himi-events-list/index.ts +47 -6
  256. package/templates/restaurant/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  257. package/templates/restaurant-owner/dev/index.ts +132 -66
  258. package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +19 -3
  259. package/templates/restaurant-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
  260. package/templates/restaurant-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  261. package/templates/restaurant-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
  262. package/templates/restaurant-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  263. package/templates/restaurant-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
  264. package/templates/restaurant-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  265. package/templates/restaurant-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
  266. package/templates/restaurant-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
  267. package/templates/restaurant-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
  268. package/templates/restaurant-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
  269. package/templates/retail-store/config.ts +7 -0
  270. package/templates/retail-store/dev/index.ts +597 -191
  271. package/templates/retail-store/himalaya.content.json +3 -0
  272. package/templates/retail-store/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  273. package/templates/retail-store/tier5-src/_shared/collection-contract.ts +19 -3
  274. package/templates/retail-store/tier5-src/_shared/himi-restaurants.ts +2 -1
  275. package/templates/retail-store/tier5-src/_shared/owner-editor-value.ts +164 -0
  276. package/templates/retail-store/tier5-src/_shared/store-product-info.ts +50 -0
  277. package/templates/retail-store/tier5-src/himi-blog-post/index.ts +21 -8
  278. package/templates/retail-store/tier5-src/himi-branded-lite-category/index.ts +3 -2
  279. package/templates/retail-store/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  280. package/templates/retail-store/tier5-src/himi-store-product/index.ts +13 -3
  281. package/templates/retail-store/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  282. package/templates/retail-store-owner/dev/index.ts +50 -1
  283. package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +19 -3
  284. package/templates/retail-store-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  285. package/templates/retail-store-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
  286. package/templates/retail-store-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  287. package/templates/retail-store-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
  288. package/templates/yoga-studio/config.ts +7 -0
  289. package/templates/yoga-studio/dev/index.ts +1107 -110
  290. package/templates/yoga-studio/himalaya.content.json +3 -0
  291. package/templates/yoga-studio/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  292. package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +19 -3
  293. package/templates/yoga-studio/tier5-src/_shared/event-registration.ts +86 -0
  294. package/templates/yoga-studio/tier5-src/_shared/himi-restaurants.ts +2 -1
  295. package/templates/yoga-studio/tier5-src/himi-blog-post/index.ts +21 -8
  296. package/templates/yoga-studio/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  297. package/templates/yoga-studio/tier5-src/himi-branded-lite-plans/index.ts +2 -1
  298. package/templates/yoga-studio/tier5-src/himi-branded-lite-program/facts.ts +52 -0
  299. package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +21 -11
  300. package/templates/yoga-studio/tier5-src/himi-events-detail/index.ts +12 -86
  301. package/templates/yoga-studio/tier5-src/himi-events-list/index.ts +47 -6
  302. package/templates/yoga-studio/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
  303. package/templates/yoga-studio/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  304. package/templates/yoga-studio-owner/dev/index.ts +85 -65
  305. package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +19 -3
  306. package/templates/yoga-studio-owner/tier5-src/_shared/owner-rich-guard.ts +27 -0
  307. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
  308. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  309. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
  310. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  311. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
  312. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
  313. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
@@ -0,0 +1,243 @@
1
+ import { defineService } from "@himalaya/state";
2
+ import { getGroup, listRules, type WixGroup, type WixGroupRule } from "@himalaya/wix-groups";
3
+ import { WIX_DETAIL_IMAGE } from "@himalaya/wix-media";
4
+ import { copyParam, stashCopy } from "../_shared/himi-copy.js";
5
+ import { groupImage } from "../_shared/himi-media.js";
6
+ import { descriptionBody } from "../_shared/rich-body.js";
7
+ import { openRichLink } from "../_shared/rich-content-link.js";
8
+ import { formatDateMedium } from "../_shared/engine-intl.js";
9
+
10
+ // The About & rules page of ONE group. The group is `input.id`, never a fallback: a missing
11
+ // id is an error, not "the first group". The group read and the rules read are independent —
12
+ // the page renders the group's own facts when only the rules read failed, and says so in the
13
+ // rules region, so a rules outage is never shown as "this group has no rules".
14
+ //
15
+ // Read-only and online-only: no write exists on this page, so there is nothing to queue.
16
+
17
+ interface RuleRow {
18
+ id: string;
19
+ number: string;
20
+ title: string;
21
+ description: string;
22
+ }
23
+
24
+ type Failure = "forbidden" | "missing" | "failed";
25
+
26
+ interface State {
27
+ status: "loading" | "ready" | "error";
28
+ id: string;
29
+ name: string;
30
+ description: string;
31
+ descriptionJson: string;
32
+ hasDescriptionJson: boolean;
33
+ privacy: string;
34
+ createdDate: string;
35
+ memberCount: string;
36
+ image: string;
37
+ initial: string;
38
+ rules: RuleRow[];
39
+ /** The group read failed: the whole page says why. */
40
+ errorText: string;
41
+ /** The rules read alone failed: only the rules region says why. */
42
+ rulesErrorText: string;
43
+ hideLoading: boolean;
44
+ hideError: boolean;
45
+ hideContent: boolean;
46
+ hideImage: boolean;
47
+ hidePrivacy: boolean;
48
+ hideCreated: boolean;
49
+ hideMemberCount: boolean;
50
+ hideRules: boolean;
51
+ hideRulesEmpty: boolean;
52
+ hideRulesError: boolean;
53
+ }
54
+
55
+ const initial: State = {
56
+ status: "loading",
57
+ id: "",
58
+ name: "",
59
+ description: "",
60
+ descriptionJson: "",
61
+ hasDescriptionJson: false,
62
+ privacy: "",
63
+ createdDate: "",
64
+ memberCount: "",
65
+ image: "",
66
+ initial: "G",
67
+ rules: [],
68
+ errorText: "",
69
+ rulesErrorText: "",
70
+ hideLoading: false,
71
+ hideError: true,
72
+ hideContent: true,
73
+ hideImage: true,
74
+ hidePrivacy: true,
75
+ hideCreated: true,
76
+ hideMemberCount: true,
77
+ hideRules: true,
78
+ hideRulesEmpty: true,
79
+ hideRulesError: true,
80
+ };
81
+
82
+ /** Bumped by every group load, so a response that arrives after a newer load began is dropped. */
83
+ let loadSeq = 0;
84
+
85
+ function inputId(): string {
86
+ // `himalaya.input.id` is the contract: hosts thread the opener's `id` nav param
87
+ // (himi.open { screenId: "himi_group_about", id }) into worker input.
88
+ const input = (globalThis as { himalaya?: { input?: { id?: string } } }).himalaya?.input;
89
+ return typeof input?.id === "string" ? input.id : "";
90
+ }
91
+
92
+ function failureOf(e: unknown): Failure {
93
+ const message = String((e as Error)?.message ?? e);
94
+ if (/HTTP (401|403)\b/.test(message)) return "forbidden";
95
+ if (/HTTP 404\b/.test(message)) return "missing";
96
+ return "failed";
97
+ }
98
+
99
+ function groupFailureText(failure: Failure): string {
100
+ if (failure === "forbidden") return copyParam("groupAbout.forbiddenTitle", "You can’t view this group’s details.");
101
+ if (failure === "missing") return copyParam("groupAbout.missingTitle", "This group no longer exists.");
102
+ return copyParam("groupAbout.errorTitle", "This group’s details could not be loaded.");
103
+ }
104
+
105
+ function rulesFailureText(failure: Failure): string {
106
+ if (failure === "forbidden") return copyParam("groupAbout.rulesForbidden", "You can’t view this group’s rules.");
107
+ return copyParam("groupAbout.rulesError", "The rules could not be loaded.");
108
+ }
109
+
110
+ /** The rules the server sent, numbered from 1. `undefined` when the answer is not a rules list
111
+ * at all — a missing or non-array `rules` is a malformed answer, not an empty group. */
112
+ function ruleRowsOf(response: unknown): RuleRow[] | undefined {
113
+ const rules = (response as { rules?: unknown } | null | undefined)?.rules;
114
+ if (!Array.isArray(rules)) return undefined;
115
+ const rows: RuleRow[] = [];
116
+ for (const entry of rules as WixGroupRule[]) {
117
+ const title = typeof entry?.title === "string" ? entry.title.trim() : "";
118
+ const description = typeof entry?.description === "string" ? entry.description.trim() : "";
119
+ if (!title && !description) continue;
120
+ rows.push({ id: `rule_${rows.length + 1}`, number: String(rows.length + 1), title, description });
121
+ }
122
+ return rows;
123
+ }
124
+
125
+ function withRules(state: State, rules: RuleRow[] | undefined, failure: Failure | undefined): State {
126
+ if (failure || !rules) {
127
+ return {
128
+ ...state,
129
+ rules: [],
130
+ rulesErrorText: rulesFailureText(failure ?? "failed"),
131
+ hideRules: true,
132
+ hideRulesEmpty: true,
133
+ hideRulesError: false,
134
+ };
135
+ }
136
+ return {
137
+ ...state,
138
+ rules,
139
+ rulesErrorText: "",
140
+ hideRules: rules.length === 0,
141
+ hideRulesEmpty: rules.length > 0,
142
+ hideRulesError: true,
143
+ };
144
+ }
145
+
146
+ function capitalized(value: string): string {
147
+ const lower = value.trim().toLowerCase();
148
+ return lower ? lower[0].toUpperCase() + lower.slice(1) : "";
149
+ }
150
+
151
+ function createdLabel(value: unknown): string {
152
+ if (typeof value !== "string" || !value) return "";
153
+ const date = new Date(value);
154
+ return Number.isNaN(date.getTime()) ? "" : formatDateMedium(date);
155
+ }
156
+
157
+ function groupState(state: State, id: string, group: WixGroup): State {
158
+ const name = (group.name || (group as { title?: string }).title || copyParam("group.groupFallbackName", "Group")).trim();
159
+ const body = descriptionBody(group.description, descriptionBody(group.teaser, "").bodyText);
160
+ const privacy = capitalized(group.privacyStatus ?? "");
161
+ const created = createdLabel(group.createdDate);
162
+ const count = group.membersCount ?? group.memberCount;
163
+ const image = groupImage(group, WIX_DETAIL_IMAGE);
164
+ return {
165
+ ...state,
166
+ status: "ready",
167
+ id,
168
+ name,
169
+ description: body.bodyText,
170
+ descriptionJson: body.bodyJson,
171
+ hasDescriptionJson: body.bodyJson !== "",
172
+ privacy,
173
+ createdDate: created,
174
+ memberCount: typeof count === "number" ? `${count} ${count === 1 ? "member" : "members"}` : "",
175
+ image,
176
+ initial: (name[0] || "G").toUpperCase(),
177
+ errorText: "",
178
+ hideLoading: true,
179
+ hideError: true,
180
+ hideContent: false,
181
+ hideImage: !image,
182
+ hidePrivacy: !privacy,
183
+ hideCreated: !created,
184
+ hideMemberCount: typeof count !== "number",
185
+ };
186
+ }
187
+
188
+ function groupErrorState(id: string, failure: Failure): State {
189
+ return {
190
+ ...initial,
191
+ id,
192
+ status: "error",
193
+ errorText: groupFailureText(failure),
194
+ hideLoading: true,
195
+ hideError: false,
196
+ hideContent: true,
197
+ };
198
+ }
199
+
200
+ const svc = defineService<State, {
201
+ load: (state: State, arg?: unknown) => Promise<State | undefined>;
202
+ reloadRules: (state: State, arg?: unknown) => Promise<State | undefined>;
203
+ openLink: (state: State, arg?: unknown) => Promise<undefined>;
204
+ }>("himiBrandedGroupAbout", {
205
+ state: initial,
206
+ actions: {
207
+ load: async (state, arg) => {
208
+ stashCopy(arg);
209
+ const seq = ++loadSeq;
210
+ const id = inputId();
211
+ if (!id) return groupErrorState("", "missing");
212
+ const [groupResult, rulesResult] = await Promise.allSettled([getGroup(id), listRules(id)]);
213
+ // A newer load began while these reads were in flight: its answer owns the state.
214
+ if (seq !== loadSeq) return undefined;
215
+ if (groupResult.status === "rejected") return groupErrorState(id, failureOf(groupResult.reason));
216
+ // Social Groups wraps the entity as `{ group }`; the typed signature claims a bare group.
217
+ const group = ((groupResult.value as unknown as { group?: WixGroup }).group ?? groupResult.value) as WixGroup;
218
+ if (!group || typeof group !== "object" || (group.id && group.id !== id)) return groupErrorState(id, "failed");
219
+ const ready = groupState(state, id, group);
220
+ return rulesResult.status === "rejected"
221
+ ? withRules(ready, undefined, failureOf(rulesResult.reason))
222
+ : withRules(ready, ruleRowsOf(rulesResult.value), undefined);
223
+ },
224
+ // Retry for the rules region alone: the group's own facts stay on screen.
225
+ reloadRules: async (state, arg) => {
226
+ stashCopy(arg);
227
+ const id = state.id;
228
+ if (!id) return undefined;
229
+ const seq = loadSeq;
230
+ try {
231
+ const rules = ruleRowsOf(await listRules(id));
232
+ if (seq !== loadSeq || inputId() !== id) return undefined;
233
+ return withRules(state, rules, undefined);
234
+ } catch (e) {
235
+ if (seq !== loadSeq || inputId() !== id) return undefined;
236
+ return withRules(state, undefined, failureOf(e));
237
+ }
238
+ },
239
+ openLink: async (_state, arg) => { await openRichLink(arg); return undefined; },
240
+ },
241
+ });
242
+
243
+ export { svc };
@@ -1,69 +1,135 @@
1
- // himi-branded-lite-loyalty — reads the real Wix Loyalty program + rewards (getLoyaltyProgram + listLoyaltyRewards).
2
- // a failed query is an error with Retry (or Sign in when Wix refuses the
3
- // visitor) and an empty one is a real empty state — never sample rows presented as
4
- // the customer's own. Samples appear only in the host's explicit sample preview
5
- // (_shared/branded-load-state.ts).
1
+ // Public Loyalty offering plus the current member's separately verified balance.
6
2
  import { defineService } from "../_shared/collection-service.js";
7
3
  import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
8
- import { getLoyaltyProgram, listLoyaltyRewards, type WixLoyaltyReward } from "@himalaya/wix-loyalty";
4
+ import { copyParam, copyTemplate } from "../_shared/himi-copy.js";
5
+ import { formatCurrency, formatNumber } from "../_shared/engine-intl.js";
6
+ import { memberAccess, assertMemberAccess } from "../_shared/member-access.js";
7
+ import { getLoyaltyProgram, listLoyaltyRewards, listLoyaltyEarningRules, getCurrentMemberLoyaltyAccount, type WixLoyaltyReward, type WixLoyaltyEarningRule } from "@himalaya/wix-loyalty";
9
8
 
10
9
  interface Row { id: string; name: string; subtitle: string }
11
10
  interface S extends LoadFlags {
12
- programName: string; unit: string;
13
- rewards: Row[]; emptyText: string;
14
- isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
15
- * shows only when the site really has none, and the unavailable one only when the
16
- * query failed. A failed query must never render the sentence that claims the
17
- * customer has no content. */
18
- notMockEmpty: boolean; notMockUnavailable: boolean;
11
+ programName: string; unit: string; currency: string;
12
+ rewards: Row[]; earningRules: Row[]; emptyText: string; earningEmptyText: string;
13
+ hideCatalog: boolean; hideEarningHeading: boolean; inactiveText: string;
14
+ rewardError: string; earningError: string; hideRewardError: boolean; hideEarningError: boolean; hideRewardRows: boolean;
15
+ memberPoints: string; memberMessage: string;
16
+ hideMemberCard: boolean; hideMemberSignIn: boolean; hideMemberRetry: boolean;
19
17
  }
18
+ const PRIVATE_EMPTY = { memberPoints: "", memberMessage: "", hideMemberCard: true, hideMemberSignIn: true, hideMemberRetry: true };
19
+ const INITIAL: S = { ...LOADING, ...PRIVATE_EMPTY, programName: "", unit: "", currency: "", rewards: [], earningRules: [], emptyText: "", earningEmptyText: "", hideCatalog: false, hideEarningHeading: true, inactiveText: "", rewardError: "", earningError: "", hideRewardError: true, hideEarningError: true, hideRewardRows: false };
20
20
 
21
- /** "500 points" / "Free" from a reward's requiredPoints. */
22
- function pointsLabel(r: WixLoyaltyReward, unit: string): string {
23
- const pts = typeof r.requiredPoints === "number" ? r.requiredPoints : undefined;
24
- if (pts === undefined) return "";
25
- if (pts <= 0) return "Free";
26
- return `${pts.toLocaleString()} ${unit}`;
21
+ function amount(value: unknown): value is number { return typeof value === "number" && Number.isFinite(value) && value >= 0; }
22
+ function baseConfig<T extends { tierId?: string }>(configs: T[] | undefined): T | undefined {
23
+ const base = configs?.filter(config => !config.tierId);
24
+ return base?.length === 1 ? base[0] : undefined;
27
25
  }
28
-
29
- function norm(r: WixLoyaltyReward, unit: string): Row {
30
- return { id: r.id || r.name || "reward", name: r.name || "Reward", subtitle: pointsLabel(r, unit) };
26
+ function points(value: number, customUnit: string): string {
27
+ const unit = customUnit || copyParam(value === 1 ? "loyalty.pointOne" : "loyalty.pointMany", value === 1 ? "point" : "points");
28
+ return copyTemplate("loyalty.pointsLabel", "{points} {unit}", { points: formatNumber(value), unit });
29
+ }
30
+ function rewardLabel(reward: WixLoyaltyReward, customUnit: string): string {
31
+ const configs = reward.discountAmount?.configsByTier ?? reward.couponReward?.fixedAmount?.configsByTier
32
+ ?? reward.couponReward?.percentage?.configsByTier ?? reward.couponReward?.freeShipping?.configsByTier;
33
+ const cost = configs?.length ? baseConfig<{ tierId?: string; costInPoints?: number }>(configs)?.costInPoints : reward.requiredPoints;
34
+ if (!amount(cost)) return copyParam("loyalty.costUnavailable", "Points details unavailable");
35
+ return cost === 0 ? copyParam("loyalty.free", "Free") : points(cost, customUnit);
36
+ }
37
+ function earningLabel(rule: WixLoyaltyEarningRule, customUnit: string, currency: string): string {
38
+ if (rule.fixedAmount) {
39
+ const value = baseConfig(rule.fixedAmount.configs)?.points;
40
+ return amount(value) ? points(value, customUnit) : copyParam("loyalty.earningUnavailable", "Points details unavailable");
41
+ }
42
+ const rate = baseConfig(rule.conversionRate?.configs);
43
+ if (!rate || !amount(rate.points) || !amount(rate.moneyAmount) || rate.moneyAmount === 0 || !currency) {
44
+ return copyParam("loyalty.earningUnavailable", "Points details unavailable");
45
+ }
46
+ return copyTemplate("loyalty.pointsPerSpend", "{points} per {amount}", {
47
+ points: points(rate.points, customUnit), amount: formatCurrency(rate.moneyAmount, currency, "en", Number.isInteger(rate.moneyAmount) ? 0 : undefined),
48
+ });
49
+ }
50
+ function businessStamp(): unknown {
51
+ return (globalThis as { himalaya?: { input?: Record<string, unknown> } }).himalaya?.input?.__himiPreferenceBusiness;
52
+ }
53
+ function currencyFrom(s: S, arg: unknown): string {
54
+ if (!arg || typeof arg !== "object" || !("currency" in arg)) return s.currency;
55
+ const raw = (arg as { currency?: unknown }).currency;
56
+ return typeof raw === "string" && /^[A-Z]{3}$/.test(raw) ? raw : "";
31
57
  }
32
-
33
- const MOCK: Row[] = [
34
- { id: "sample-5off", name: "5% off your next order", subtitle: "500 points" },
35
- { id: "sample-ship", name: "Free shipping", subtitle: "300 points" },
36
- { id: "sample-15", name: "15% off any purchase", subtitle: "750 points" },
37
- { id: "sample-gift", name: "Free welcome gift", subtitle: "1,000 points" },
38
- ];
39
-
40
- /** sample rows reach the screen ONLY in the host's explicit sample preview;
41
- * a failed or empty query is an honest error / empty state (branded-load-state.ts). */
42
58
  const COPY: LoadCopy<Row> = {
43
- prefix: "loyalty",
44
- empty: "No rewards to redeem yet.",
45
- error: "The rewards program couldn't be loaded.",
46
- signIn: "Sign in to see your points and rewards.",
47
- sample: () => MOCK.map((r) => ({ ...r })),
59
+ prefix: "loyalty", empty: "No rewards to redeem yet.",
60
+ error: "The rewards program couldn't be loaded.", signIn: "The rewards program couldn't be loaded.",
61
+ sample: () => [{ id: "sample-reward", name: copyParam("loyalty.sampleReward", "5% off your next order"), subtitle: points(500, "") }],
48
62
  };
49
63
 
50
- const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedLoyalty", {
51
- state: { ...LOADING, programName: "Rewards", unit: "points", rewards: [], emptyText: "" },
64
+ const svc = defineService<S, { prepare: (s: S) => S; load: (s: S, arg?: unknown) => Promise<S> }>("himiBrandedLoyalty", {
65
+ state: INITIAL,
66
+ notHydrated: ["memberPoints", "memberMessage", "hideMemberCard", "hideMemberSignIn", "hideMemberRetry"],
67
+ notPersisted: ["memberPoints", "memberMessage", "hideMemberCard", "hideMemberSignIn", "hideMemberRetry"],
52
68
  actions: {
53
- load: async (s) => {
69
+ prepare: s => ({ ...s, ...PRIVATE_EMPTY }),
70
+ load: async (s, arg) => {
71
+ const currency = currencyFrom(s, arg);
72
+ const business = businessStamp();
54
73
  try {
55
- const prog = await getLoyaltyProgram();
56
- const p = prog.loyaltyProgram ?? prog.program;
57
- const unit = (p?.pointDefinition?.customName || "points").trim() || "points";
58
- const programName = (p?.name || "Rewards").trim() || "Rewards";
59
- const r = await listLoyaltyRewards();
60
- const rewards = (r.rewards ?? []).filter((x) => x.active !== false).map((x) => norm(x, unit));
61
- return { ...s, programName, unit, ...loaded("rewards", rewards, COPY) };
62
- } catch (err) {
63
- return { ...s, ...loadFailed("rewards", err, COPY) };
74
+ const [response, member] = await Promise.all([getLoyaltyProgram(), memberAccess()]);
75
+ const program = response.loyaltyProgram ?? response.program;
76
+ if (!program || !["ACTIVE", "DRAFT", "PAUSED"].includes(program.status ?? "")) throw new Error("Loyalty program response is unavailable");
77
+ const customUnit = (program.pointDefinition?.customName ?? "").trim();
78
+ const common = { ...s, ...PRIVATE_EMPTY, currency, programName: program.name || copyParam("loyalty.programFallback", "Rewards"), unit: customUnit || copyParam("loyalty.pointMany", "points"), inactiveText: "", rewardError: "", earningError: "", hideRewardError: true, hideEarningError: true, hideRewardRows: true, hideEarningHeading: true };
79
+ if (program.status !== "ACTIVE") {
80
+ return { ...common, ...loaded("rewards", [], COPY), earningRules: [], earningEmptyText: "", hideCatalog: true,
81
+ inactiveText: copyParam("loyalty.inactive", "The rewards program isn't available right now.") };
82
+ }
83
+ // Balance failures cannot discard a public offering that loaded correctly.
84
+ const privateStateSupported = !!member.scope && ((globalThis as { himalaya?: { readLifecycleVersion?: number } }).himalaya?.readLifecycleVersion ?? 0) >= 1;
85
+ const accountRead = member.email && privateStateSupported
86
+ ? getCurrentMemberLoyaltyAccount().then(value => ({ value, failed: false })).catch(() => ({ value: undefined, failed: true }))
87
+ : Promise.resolve({ value: undefined, failed: false });
88
+ const [[rewardResult, earningResult], account] = await Promise.all([
89
+ Promise.allSettled([listLoyaltyRewards(), listLoyaltyEarningRules()]), accountRead,
90
+ ]);
91
+ const rewardResponse = rewardResult.status === "fulfilled" ? rewardResult.value : undefined;
92
+ const earningResponse = earningResult.status === "fulfilled" ? earningResult.value : undefined;
93
+ if (businessStamp() !== business) throw new Error("Business changed while Loyalty loaded");
94
+ if (rewardResult.status === "rejected" && earningResult.status === "rejected") throw new Error("Loyalty catalog is unavailable");
95
+ const rewards = (rewardResponse?.rewards ?? []).filter(reward => reward.active === true).map((reward, i) => ({
96
+ id: reward.id || `reward-${i}`, name: reward.name || copyParam("loyalty.rewardFallback", "Reward"), subtitle: rewardLabel(reward, customUnit),
97
+ }));
98
+ const earningRules = (earningResponse?.earningRules ?? []).filter(rule => rule.status === "ACTIVE").map((rule, i) => ({
99
+ id: rule.id || `earning-${i}`, name: rule.title || copyParam("loyalty.earnFallback", "Earn points"), subtitle: earningLabel(rule, customUnit, currency),
100
+ }));
101
+ let privateState = { ...PRIVATE_EMPTY };
102
+ if (member.email) {
103
+ try {
104
+ await assertMemberAccess(member);
105
+ const balance = account.value?.account?.points?.balance;
106
+ privateState = { ...PRIVATE_EMPTY, hideMemberCard: false,
107
+ memberPoints: typeof balance === "number" && Number.isFinite(balance) ? points(balance, customUnit) : "",
108
+ memberMessage: !privateStateSupported ? copyParam("loyalty.updateApp", "Update the app to see your points securely.") : account.failed ? copyParam("loyalty.pointsError", "Your points couldn't be loaded.")
109
+ : typeof balance !== "number" || !Number.isFinite(balance) ? copyParam("loyalty.pointsUnavailable", "Your points aren't available yet.") : "",
110
+ hideMemberRetry: !account.failed };
111
+ } catch {
112
+ const current = await memberAccess();
113
+ privateState = { ...PRIVATE_EMPTY, hideMemberCard: !current.email, hideMemberSignIn: !!current.email,
114
+ memberMessage: current.email ? copyParam("loyalty.pointsError", "Your points couldn't be loaded.") : "", hideMemberRetry: !current.email };
115
+ }
116
+ } else {
117
+ privateState.hideMemberSignIn = false;
118
+ }
119
+ return { ...common, ...loaded("rewards", rewards, COPY), ...privateState, earningRules, hideCatalog: false, hideEarningHeading: false,
120
+ rewardError: rewardResult.status === "rejected" ? copyParam("loyalty.rewardsError", "Rewards couldn't be loaded.") : "",
121
+ earningError: earningResult.status === "rejected" ? copyParam("loyalty.earningError", "Ways to earn points couldn't be loaded.") : "",
122
+ hideRewardError: rewardResult.status === "fulfilled", hideEarningError: earningResult.status === "fulfilled", hideRewardRows: rewardResult.status === "rejected",
123
+ emptyText: rewardResult.status === "rejected" ? "" : rewards.length ? "" : copyParam("loyalty.emptyText", COPY.empty),
124
+ earningEmptyText: earningResult.status === "rejected" || earningRules.length ? "" : copyParam("loyalty.earningEmpty", "No ways to earn points yet.") };
125
+ } catch (error) {
126
+ const failure = loadFailed("rewards", error, COPY);
127
+ return { ...s, ...PRIVATE_EMPTY, currency, earningRules: [], earningEmptyText: "", inactiveText: "", hideCatalog: !failure.isMock, hideEarningHeading: true, rewardError: "", earningError: "", hideRewardError: true, hideEarningError: true, hideRewardRows: !failure.isMock,
128
+ ...failure, signInRequired: false, noSignIn: true };
64
129
  }
65
130
  },
66
131
  },
67
132
  });
133
+ svc.prepare();
68
134
  svc.load();
69
135
  export { svc };
@@ -1,3 +1,4 @@
1
+ import { descriptionBody } from "../_shared/rich-body.js";
1
2
  import { paymentSetupMessage } from "../_shared/payment-setup.js";
2
3
  import { offlineWriteMessage, offlineWritesEnabled, withOnlineWrites } from "../_shared/online-actions.js";
3
4
  import { connectivity } from "@himalaya/state";
@@ -140,7 +141,7 @@ function planRow(plan: WixPlan): PlanRow {
140
141
  return {
141
142
  id: plan.id || "",
142
143
  name,
143
- description: (plan.description ?? "").trim(),
144
+ description: descriptionBody(plan.description).bodyText,
144
145
  trialBadge,
145
146
  noTrialBadge: !trialBadge,
146
147
  priceLine: money(plan),
@@ -1,3 +1,4 @@
1
+ import { descriptionBody } from "../_shared/rich-body.js";
1
2
  // himi-branded-lite-podcasts — live RSS episodes with a persistent queue and
2
3
  // the shared native/web media playback transport.
3
4
  import { defineService } from "../_shared/collection-service.js";
@@ -99,7 +100,7 @@ function norm(episode: Episode): Row | null {
99
100
  title: (episode.title || "Episode").trim() || "Episode",
100
101
  subtitle: [dateLabel(episode.date), duration ? clock(duration) : ""].filter(Boolean).join(" · "),
101
102
  image: (episode.image || "").trim(),
102
- description: (episode.descriptionExcerpt || "").trim(),
103
+ description: descriptionBody(episode.descriptionExcerpt).bodyText,
103
104
  audioUrl,
104
105
  durationMs: duration,
105
106
  progressLabel: "",
@@ -1,3 +1,4 @@
1
+ import { descriptionBody } from "../_shared/rich-body.js";
1
2
  // himi-branded-lite-portfolio — real, public Wix Portfolio projects.
2
3
  // Empty and error states stay explicit; media IDs are normalized for every host.
3
4
  import { defineService } from "../_shared/collection-service.js";
@@ -28,7 +29,7 @@ function norm(project: WixPortfolioProject): Row {
28
29
  return {
29
30
  id: project.id || project.slug || title,
30
31
  title,
31
- subtitle: (project.description || "").trim(),
32
+ subtitle: descriptionBody(project.description).bodyText,
32
33
  image,
33
34
  initial: (title[0] || "#").toUpperCase(),
34
35
  hasImage: Boolean(image),
@@ -1,3 +1,4 @@
1
+ import { descriptionBody } from "../_shared/rich-body.js";
1
2
  // himi-branded-lite-portfolio-project — real Wix Portfolio project detail.
2
3
  // The public project row is resolved again from Wix, then its ordered project
3
4
  // items supply the actual image/video/text story. No sample project is invented.
@@ -59,7 +60,7 @@ function row(item: WixPortfolioProjectItem, index: number): MediaRow {
59
60
  return {
60
61
  id: item.id || `portfolio-item-${index}`,
61
62
  title: (item.title ?? "").trim(),
62
- description: (item.description ?? "").trim(),
63
+ description: descriptionBody(item.description).bodyText,
63
64
  image,
64
65
  videoUrl: hasVideo ? videoUrl : "",
65
66
  hasImage,
@@ -132,7 +133,7 @@ const svc = defineService<State, { load: (state: State, arg?: unknown) => Promis
132
133
  id,
133
134
  title: (project.title || copyParam("portfolioProject.projectFallbackName", "Project")).trim(),
134
135
  initial: ((project.title || "P").trim()[0] || "P").toUpperCase(),
135
- description: (project.description ?? "").trim(),
136
+ description: descriptionBody(project.description).bodyText,
136
137
  coverImage: wixMediaUrl(portfolioCoverRef(project.coverImage), WIX_DETAIL_IMAGE),
137
138
  items,
138
139
  emptyText: items.length ? "" : copyParam("portfolioProject.noMediaText", "This project has no published media yet."),
@@ -0,0 +1,52 @@
1
+ import type { WixProgramFull } from "@himalaya/wix-online-programs";
2
+ import { copyParam } from "../_shared/himi-copy.js";
3
+ import { formatDateMedium } from "../_shared/engine-intl.js";
4
+
5
+ export interface ProgramFact { id: string; icon: string; title: string; body: string }
6
+
7
+ /** Only explicit Program fields establish a fact. Missing access is never public. */
8
+ export function programFacts(program: WixProgramFull, locale: string): ProgramFact[] {
9
+ const facts: ProgramFact[] = [];
10
+ const timeline = program.timeline;
11
+ if (timeline?.selfPaced === true) facts.push({
12
+ id: "pace", icon: "clock",
13
+ title: copyParam("program.facts.selfPacedTitle", "Self-paced"),
14
+ body: copyParam("program.facts.selfPacedBody", "Complete steps at your own pace."),
15
+ });
16
+ else if (timeline?.selfPaced === false) facts.push({
17
+ id: "pace", icon: "clock",
18
+ title: copyParam("program.facts.scheduledTitle", "Scheduled"),
19
+ body: copyParam("program.facts.scheduledBody", "Follow the program schedule."),
20
+ });
21
+
22
+ const rawStart = timeline?.startDate;
23
+ // A date-only field is a calendar day, not midnight in the viewer's time zone. Build it at LOCAL noon and
24
+ // format it in the local zone: that reads the same day with ECMA-402 and on engines without it (watch/Wear),
25
+ // whose fallback ignores a timeZone argument and reads local getters.
26
+ const dateOnly = typeof rawStart === "string" ? /^(\d{4})-(\d{2})-(\d{2})$/.exec(rawStart.trim()) : null;
27
+ const date = dateOnly ? new Date(Number(dateOnly[1]), Number(dateOnly[2]) - 1, Number(dateOnly[3]), 12)
28
+ : typeof rawStart === "string" && rawStart.trim() ? new Date(rawStart) : undefined;
29
+ if (date && Number.isFinite(date.getTime())) {
30
+ let body: string;
31
+ try { body = formatDateMedium(date, locale); }
32
+ catch { body = formatDateMedium(date, "en"); }
33
+ facts.push({ id: "start", icon: "calendar", title: copyParam("program.facts.fixedStartTitle", "Fixed start date"), body });
34
+ } else if (timeline?.selfPaced === true && (rawStart === undefined || rawStart === "")) facts.push({
35
+ id: "start", icon: "calendar",
36
+ title: copyParam("program.facts.flexibleStartTitle", "Flexible start date"),
37
+ body: copyParam("program.facts.flexibleStartBody", "Start whenever you're ready."),
38
+ });
39
+
40
+ const access = program.restrictions?.accessType?.toUpperCase();
41
+ if (access === "PUBLIC") facts.push({
42
+ id: "access", icon: "globe",
43
+ title: copyParam("program.facts.publicTitle", "Public"),
44
+ body: copyParam("program.facts.publicBody", "Anyone can join."),
45
+ });
46
+ else if (access === "PRIVATE") facts.push({
47
+ id: "access", icon: "lock",
48
+ title: copyParam("program.facts.privateTitle", "Private"),
49
+ body: copyParam("program.facts.privateBody", "Approval is required to join."),
50
+ });
51
+ return facts;
52
+ }
@@ -40,7 +40,8 @@ import {
40
40
  } from "@himalaya/wix-online-programs";
41
41
  import { createRedirectSession } from "@himalaya/wix-redirects";
42
42
  import { getMyMember } from "@himalaya/wix-members";
43
- import { session as wixPlatformSession } from "@himalaya/wix-platform";
43
+ import { session as wixPlatformSession, i18n } from "@himalaya/wix-platform";
44
+ import { programFacts, type ProgramFact } from "./facts.js";
44
45
  import { cleanTitle, initials } from "../_shared/himi-text.js";
45
46
  import { copyParam, copyTemplate, stashCopy } from "../_shared/himi-copy.js";
46
47
  import { isPaymentSetupFailure, payCashierOrder } from "@himalaya/checkout";
@@ -62,8 +63,10 @@ interface State {
62
63
  description: string;
63
64
  descriptionJson: string;
64
65
  hasDescriptionJson: boolean;
65
- /** "N sections · M steps" / "Self-paced" — structure meta line. */
66
+ /** "N sections · M steps" — structure meta line; pace has its own fact row. */
66
67
  meta: string;
68
+ facts: ProgramFact[];
69
+ hideFacts: boolean;
67
70
  /** Formatted price when the payload carries one ("" for free/absent). */
68
71
  priceLine: string;
69
72
  image: string;
@@ -105,8 +108,11 @@ function inputId(): string {
105
108
 
106
109
  function derive(state: State): State {
107
110
  const empty = state.status === "ready" && !state.name;
111
+ const facts = Array.isArray(state.facts) ? state.facts : [];
108
112
  return {
109
113
  ...state,
114
+ facts,
115
+ hideFacts: facts.length === 0,
110
116
  hasDescriptionJson: state.descriptionJson !== "",
111
117
  isLoading: state.status === "loading",
112
118
  notLoading: state.status !== "loading",
@@ -132,9 +138,7 @@ function metaOf(p: WixProgram): string {
132
138
  const steps = Number(summary?.stepCount ?? 0);
133
139
  if (sections) bits.push(`${sections} ${sections === 1 ? "section" : "sections"}`);
134
140
  if (steps) bits.push(`${steps} ${steps === 1 ? "step" : "steps"}`);
135
- if (bits.length) return bits.join(" · ");
136
- const selfPaced = (p as { timeline?: { selfPaced?: boolean } }).timeline?.selfPaced;
137
- return selfPaced ? copyParam("program.selfPacedLabel", "Self-paced") : "";
141
+ return bits.join(" · ");
138
142
  }
139
143
 
140
144
  function priceOf(p: WixProgram): string {
@@ -149,10 +153,10 @@ function priceOf(p: WixProgram): string {
149
153
  }
150
154
  }
151
155
 
152
- function ready(state: State, p: WixProgram | undefined): State {
156
+ function ready(state: State, p: WixProgram | undefined, locale: string): State {
153
157
  const stateOf = ((p?.state ?? p?.status ?? "") as string).toUpperCase();
154
158
  if (!p || stateOf === "DRAFT") {
155
- return derive({ ...state, status: "ready", name: "", description: "", descriptionJson: "", meta: "", priceLine: "", image: "", initial: "", emptyText: copyParam("program.emptyText", "This program is no longer available.") });
159
+ return derive({ ...state, status: "ready", name: "", description: "", descriptionJson: "", meta: "", facts: [], priceLine: "", image: "", initial: "", emptyText: copyParam("program.emptyText", "This program is no longer available.") });
156
160
  }
157
161
  const name = titleOf(p);
158
162
  const image = programImage(p, WIX_DETAIL_IMAGE);
@@ -164,6 +168,7 @@ function ready(state: State, p: WixProgram | undefined): State {
164
168
  description: body.bodyText,
165
169
  descriptionJson: body.bodyJson,
166
170
  meta: metaOf(p),
171
+ facts: programFacts(p, locale),
167
172
  priceLine: priceOf(p),
168
173
  image,
169
174
  initial: initials(name),
@@ -274,7 +279,7 @@ const pricing = commerce.pricing;
274
279
  const PRICING_NOT_HYDRATED = [
275
280
  "priceLine", "priceNote", "hidePriceRetry", "enrollDisabled", "enrollNote", "enrollHidden", "buyHidden", "buyLabel", "buyDisabled", "buyNote",
276
281
  ] as const;
277
- const CONTENT_KEYS = ["status", "name", "description", "descriptionJson", "meta", "image", "initial", "emptyText"] as const;
282
+ const CONTENT_KEYS = ["status", "name", "description", "descriptionJson", "meta", "facts", "image", "initial", "emptyText"] as const;
278
283
  type Content = Pick<State, (typeof CONTENT_KEYS)[number]>;
279
284
  const NO_PRICE: IapPriceFields = { priceText: "", priceNote: "", priceLoading: false, hidePriceRetry: true, actionDisabled: true, actionNote: "" };
280
285
  const NOT_APPLE: IapPurchaseFields = { appleRoute: false, accessReady: false, buyLabel: "", buyDisabled: true, buyBusy: false, buyNote: "" };
@@ -307,9 +312,12 @@ async function load(state: State, arg?: unknown): Promise<undefined> {
307
312
  if (presentation === "sheet" || presentation === "full_screen") signInPresentation = presentation;
308
313
  const id = inputId();
309
314
  const seq = ++loadSeq;
310
- const found = await fetchProgram(inputId()).then((program) => ({ program }), () => undefined);
311
- if (seq !== loadSeq) return undefined;
312
- content = found ? contentOf(ready(state, found.program)) : { ...content, status: "error" };
315
+ const [found, locale] = await Promise.all([
316
+ fetchProgram(id).then((program) => ({ program }), () => undefined),
317
+ i18n.locale().catch(() => "en"),
318
+ ]);
319
+ if (seq !== loadSeq || inputId() !== id) return undefined;
320
+ content = found ? contentOf(ready(state, found.program, locale)) : { ...content, status: "error" };
313
321
  pricedItem = found?.program ? onlineProgramItem({ id: inputId() }, priceOf(found.program)) : pricedItem;
314
322
  void svc.pricingPublish();
315
323
  const prices = found?.program ? priceProgram() : Promise.resolve();
@@ -467,6 +475,8 @@ const INITIAL_STATE: State = derive({
467
475
  descriptionJson: "",
468
476
  hasDescriptionJson: false,
469
477
  meta: "",
478
+ facts: [],
479
+ hideFacts: true,
470
480
  priceLine: "",
471
481
  image: "",
472
482
  initial: "",