@wix/himalaya-cli 0.1035.0 → 0.1037.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.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/builder.d.ts +7 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/events/src/display.d.ts +13 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/portfolio/src/index.d.ts +46 -9
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-events.mjs +28 -6
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-portfolio.mjs +14 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-stores-collections.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-stores.mjs +7 -1
- package/dist/cli.mjs +482 -113
- package/dist/preview-assets.generated.bin +0 -0
- package/fixtures/wix-canned.json +51 -2
- package/guides/sdk-reference.json +32 -14
- package/guides/wix-business-context.md +13 -1
- package/guides/wix-site-to-himi-app.md +10 -1
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +72 -175
- package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/beauty-salon/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/beauty-salon/tier5-src/_shared/himi-restaurants.ts +4 -4
- package/templates/beauty-salon/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/beauty-salon/tier5-src/_shared/wix-service-price.ts +1 -1
- package/templates/beauty-salon/tier5-src/himi-booking-bookings/index.ts +14 -8
- package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/beauty-salon/tier5-src/himi-branded-lite-info/index.ts +5 -2
- package/templates/beauty-salon/tier5-src/himi-branded-lite-plans/index.ts +1 -1
- package/templates/beauty-salon/tier5-src/wix-spaces-collections/index.ts +1 -1
- package/templates/beauty-salon/tier5-src/wix-spaces-whats-new/index.ts +4 -4
- package/templates/beauty-salon-owner/dev/index.ts +84 -599
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +22 -6
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/beauty-salon-owner/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
- package/templates/branded-lite/dev/index.ts +178 -264
- package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/branded-lite/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/branded-lite/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/branded-lite/tier5-src/_shared/himi-restaurants.ts +4 -4
- package/templates/branded-lite/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/branded-lite/tier5-src/_shared/wix-service-price.ts +1 -1
- package/templates/branded-lite/tier5-src/himi-booking-bookings/index.ts +14 -8
- package/templates/branded-lite/tier5-src/himi-branded-lite-cms/index.ts +5 -5
- package/templates/branded-lite/tier5-src/himi-branded-lite-coupons/index.ts +33 -7
- package/templates/branded-lite/tier5-src/himi-branded-lite-donations/index.ts +3 -3
- package/templates/branded-lite/tier5-src/himi-branded-lite-forms/index.ts +11 -10
- package/templates/branded-lite/tier5-src/himi-branded-lite-giftcards/index.ts +3 -3
- package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-info/index.ts +5 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-invoices/index.ts +3 -3
- package/templates/branded-lite/tier5-src/himi-branded-lite-loyalty/index.ts +1 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-plans/index.ts +1 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-portfolio/index.ts +2 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-portfolio-project/index.ts +3 -3
- package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +1 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-reserve/index.ts +5 -3
- package/templates/branded-lite/tier5-src/himi-events-detail/index.ts +1 -1
- package/templates/branded-lite/tier5-src/himi-events-list/index.ts +20 -4
- package/templates/branded-lite/tier5-src/himi-events-my-events/index.ts +2 -2
- package/templates/branded-lite/tier5-src/wix-spaces-collections/index.ts +1 -1
- package/templates/branded-lite/tier5-src/wix-spaces-forum/index.ts +7 -1
- package/templates/branded-lite/tier5-src/wix-spaces-whats-new/index.ts +4 -4
- package/templates/community-club/dev/index.ts +62 -81
- package/templates/community-club/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/community-club/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/community-club/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/community-club/tier5-src/_shared/himi-restaurants.ts +4 -4
- package/templates/community-club/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/community-club/tier5-src/_shared/wix-service-price.ts +1 -1
- package/templates/community-club/tier5-src/himi-branded-lite-cms/index.ts +5 -5
- package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/community-club/tier5-src/himi-branded-lite-info/index.ts +5 -2
- package/templates/community-club/tier5-src/himi-events-detail/index.ts +1 -1
- package/templates/community-club/tier5-src/himi-events-list/index.ts +20 -4
- package/templates/community-club/tier5-src/himi-events-my-events/index.ts +2 -2
- package/templates/community-club/tier5-src/wix-spaces-whats-new/index.ts +4 -4
- package/templates/community-club-owner/dev/index.ts +84 -599
- package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +22 -6
- package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/community-club-owner/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/community-club-owner/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/community-club-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/community-club-owner/tier5-src/himi-owner-lite-events/index.ts +1 -0
- package/templates/events-meetups/dev/index.ts +60 -79
- package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/events-meetups/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/events-meetups/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/events-meetups/tier5-src/_shared/himi-restaurants.ts +4 -4
- package/templates/events-meetups/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/events-meetups/tier5-src/_shared/wix-service-price.ts +1 -1
- package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/events-meetups/tier5-src/himi-branded-lite-info/index.ts +5 -2
- package/templates/events-meetups/tier5-src/himi-events-detail/index.ts +1 -1
- package/templates/events-meetups/tier5-src/himi-events-list/index.ts +20 -4
- package/templates/events-meetups/tier5-src/himi-events-my-events/index.ts +2 -2
- package/templates/events-meetups/tier5-src/wix-spaces-whats-new/index.ts +4 -4
- package/templates/events-meetups-owner/dev/index.ts +84 -543
- package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +22 -6
- package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/events-meetups-owner/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/events-meetups-owner/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/events-meetups-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-events/index.ts +1 -0
- package/templates/gym-fitness/dev/index.ts +73 -204
- package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/gym-fitness/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/gym-fitness/tier5-src/_shared/himi-restaurants.ts +4 -4
- package/templates/gym-fitness/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/gym-fitness/tier5-src/_shared/wix-service-price.ts +1 -1
- package/templates/gym-fitness/tier5-src/himi-booking-bookings/index.ts +14 -8
- package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/gym-fitness/tier5-src/himi-branded-lite-info/index.ts +5 -2
- package/templates/gym-fitness/tier5-src/himi-branded-lite-plans/index.ts +1 -1
- package/templates/gym-fitness/tier5-src/himi-events-detail/index.ts +1 -1
- package/templates/gym-fitness/tier5-src/himi-events-list/index.ts +20 -4
- package/templates/gym-fitness/tier5-src/himi-events-my-events/index.ts +2 -2
- package/templates/gym-fitness/tier5-src/wix-spaces-collections/index.ts +1 -1
- package/templates/gym-fitness/tier5-src/wix-spaces-whats-new/index.ts +4 -4
- package/templates/gym-fitness-owner/dev/index.ts +84 -627
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +22 -6
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/gym-fitness-owner/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-events/index.ts +1 -0
- package/templates/himi-chat/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/nonprofit-cause/dev/index.ts +71 -90
- package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/nonprofit-cause/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/nonprofit-cause/tier5-src/_shared/himi-restaurants.ts +4 -4
- package/templates/nonprofit-cause/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/nonprofit-cause/tier5-src/_shared/wix-service-price.ts +1 -1
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-info/index.ts +5 -2
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-plans/index.ts +1 -1
- package/templates/nonprofit-cause/tier5-src/himi-events-detail/index.ts +1 -1
- package/templates/nonprofit-cause/tier5-src/himi-events-list/index.ts +20 -4
- package/templates/nonprofit-cause/tier5-src/himi-events-my-events/index.ts +2 -2
- package/templates/nonprofit-cause/tier5-src/wix-spaces-whats-new/index.ts +4 -4
- package/templates/nonprofit-cause-owner/dev/index.ts +84 -571
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +22 -6
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/nonprofit-cause-owner/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-events/index.ts +1 -0
- package/templates/owner-lite/dev/index.ts +84 -627
- package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +22 -6
- package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/owner-lite/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/owner-lite/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/owner-lite/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/owner-lite/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/owner-lite/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
- package/templates/owner-lite/tier5-src/himi-owner-lite-events/index.ts +1 -0
- package/templates/podcast-creator/dev/index.ts +71 -90
- package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/podcast-creator/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/podcast-creator/tier5-src/_shared/himi-restaurants.ts +4 -4
- package/templates/podcast-creator/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/podcast-creator/tier5-src/_shared/wix-service-price.ts +1 -1
- package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/podcast-creator/tier5-src/himi-branded-lite-info/index.ts +5 -2
- package/templates/podcast-creator/tier5-src/himi-branded-lite-plans/index.ts +1 -1
- package/templates/podcast-creator/tier5-src/himi-events-detail/index.ts +1 -1
- package/templates/podcast-creator/tier5-src/himi-events-list/index.ts +20 -4
- package/templates/podcast-creator/tier5-src/himi-events-my-events/index.ts +2 -2
- package/templates/podcast-creator/tier5-src/wix-spaces-whats-new/index.ts +4 -4
- package/templates/podcast-creator-owner/dev/index.ts +84 -599
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +22 -6
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/podcast-creator-owner/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-events/index.ts +1 -0
- package/templates/portfolio-creative/dev/index.ts +62 -137
- package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/portfolio-creative/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/portfolio-creative/tier5-src/_shared/himi-restaurants.ts +4 -4
- package/templates/portfolio-creative/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/portfolio-creative/tier5-src/_shared/wix-service-price.ts +1 -1
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-info/index.ts +5 -2
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-portfolio/index.ts +2 -2
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-portfolio-project/index.ts +3 -3
- package/templates/portfolio-creative/tier5-src/himi-events-detail/index.ts +1 -1
- package/templates/portfolio-creative/tier5-src/himi-events-list/index.ts +20 -4
- package/templates/portfolio-creative/tier5-src/himi-events-my-events/index.ts +2 -2
- package/templates/portfolio-creative/tier5-src/wix-spaces-collections/index.ts +1 -1
- package/templates/portfolio-creative/tier5-src/wix-spaces-whats-new/index.ts +4 -4
- package/templates/portfolio-creative-owner/dev/index.ts +84 -599
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +22 -6
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/portfolio-creative-owner/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
- package/templates/restaurant/dev/index.ts +124 -101
- package/templates/restaurant/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/restaurant/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/restaurant/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/restaurant/tier5-src/_shared/himi-restaurants.ts +4 -4
- package/templates/restaurant/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/restaurant/tier5-src/_shared/wix-service-price.ts +1 -1
- package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/restaurant/tier5-src/himi-branded-lite-info/index.ts +5 -2
- package/templates/restaurant/tier5-src/himi-branded-lite-reserve/index.ts +5 -3
- package/templates/restaurant/tier5-src/himi-events-detail/index.ts +1 -1
- package/templates/restaurant/tier5-src/himi-events-list/index.ts +20 -4
- package/templates/restaurant/tier5-src/himi-events-my-events/index.ts +2 -2
- package/templates/restaurant/tier5-src/wix-spaces-whats-new/index.ts +4 -4
- package/templates/restaurant-owner/dev/index.ts +84 -627
- package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +22 -6
- package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/restaurant-owner/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/restaurant-owner/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/restaurant-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-events/index.ts +1 -0
- package/templates/retail-store/dev/index.ts +59 -106
- package/templates/retail-store/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/retail-store/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/retail-store/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/retail-store/tier5-src/_shared/himi-restaurants.ts +4 -4
- package/templates/retail-store/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/retail-store/tier5-src/_shared/wix-service-price.ts +1 -1
- package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/retail-store/tier5-src/himi-branded-lite-info/index.ts +5 -2
- package/templates/retail-store/tier5-src/wix-spaces-collections/index.ts +1 -1
- package/templates/retail-store/tier5-src/wix-spaces-whats-new/index.ts +4 -4
- package/templates/retail-store-owner/dev/index.ts +84 -571
- package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +22 -6
- package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/retail-store-owner/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/retail-store-owner/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/retail-store-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/yoga-studio/dev/index.ts +73 -148
- package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/yoga-studio/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/yoga-studio/tier5-src/_shared/himi-restaurants.ts +4 -4
- package/templates/yoga-studio/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/yoga-studio/tier5-src/_shared/wix-service-price.ts +1 -1
- package/templates/yoga-studio/tier5-src/himi-booking-bookings/index.ts +14 -8
- package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/yoga-studio/tier5-src/himi-branded-lite-info/index.ts +5 -2
- package/templates/yoga-studio/tier5-src/himi-branded-lite-plans/index.ts +1 -1
- package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +1 -1
- package/templates/yoga-studio/tier5-src/himi-events-detail/index.ts +1 -1
- package/templates/yoga-studio/tier5-src/himi-events-list/index.ts +20 -4
- package/templates/yoga-studio/tier5-src/himi-events-my-events/index.ts +2 -2
- package/templates/yoga-studio/tier5-src/wix-spaces-whats-new/index.ts +4 -4
- package/templates/yoga-studio-owner/dev/index.ts +84 -571
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +22 -6
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +16 -9
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +19 -5
- package/templates/yoga-studio-owner/tier5-src/_shared/engine-intl.ts +51 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-load-error.ts +49 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-events/index.ts +1 -0
|
@@ -26,6 +26,13 @@ export interface CollectionConfig {
|
|
|
26
26
|
reviewSelectedAction?: string;
|
|
27
27
|
/** Optional comfortable grid media height for immersive visual catalogs. */
|
|
28
28
|
gridImageHeight?: number;
|
|
29
|
+
/** What the count summary counts — ["dish", "dishes"] reads "12 dishes". Default "item(s)". */
|
|
30
|
+
countNoun?: [string, string];
|
|
31
|
+
/** Phone grid columns (default 2). A card carrying a paragraph of benefits and a CTA — a
|
|
32
|
+
* pricing plan — is unreadable at half width, so it opts into one. */
|
|
33
|
+
gridColumns?: number;
|
|
34
|
+
/** Lines of the meta caption a comfortable row may use (default 2). */
|
|
35
|
+
metaMaxLines?: number;
|
|
29
36
|
/** The card's primary verb, shown as a visible button beside the overflow.
|
|
30
37
|
* `screenId` opens that screen with the row id; without it the verb reuses the
|
|
31
38
|
* row's own tap action (the detail screen that owns the verb). */
|
|
@@ -38,28 +45,28 @@ export interface CollectionConfig {
|
|
|
38
45
|
* page, the list/grid toggle, Refine, chips, search and count are noise. */
|
|
39
46
|
export const COLLECTION_CHROME_MIN_ITEMS = 4;
|
|
40
47
|
export const COLLECTIONS: Record<string, CollectionConfig> = {
|
|
41
|
-
himiStoreShop: { rows: 'results', title: 'name', subtitle: 'subtitle', image: 'image', view: 'grid', backendQuery: true },
|
|
42
|
-
himiBookingServices: { rows: 'results', title: 'name', subtitle: 'subtitle', image: 'image', filter: 'category', view: 'grid', backendQuery: true, searchPlaceholder: '
|
|
48
|
+
himiStoreShop: { rows: 'results', title: 'name', subtitle: 'subtitle', image: 'image', view: 'grid', backendQuery: true, countNoun: ['product', 'products'] },
|
|
49
|
+
himiBookingServices: { rows: 'results', title: 'name', subtitle: 'subtitle', image: 'image', filter: 'category', view: 'grid', backendQuery: true, searchPlaceholder: 'Search services', countNoun: ['service', 'services'], primaryVerb: { label: 'Book', screenId: 'himi_time' } },
|
|
43
50
|
himiBookingBookings: { rows: 'rows', title: 'title', subtitle: 'subtitle', metadata: ['status'], view: 'list', backendQuery: true,
|
|
44
|
-
defaultFilter: 'value:Upcoming', searchPlaceholder: '
|
|
51
|
+
defaultFilter: 'value:Upcoming', searchPlaceholder: 'Search by booking ID', countNoun: ['booking', 'bookings'], filterValues: ['Upcoming', 'Past', 'Cancelled'],
|
|
45
52
|
sortLabels: { default: 'Soonest', az: 'Oldest', za: 'Latest' } },
|
|
46
|
-
himiEvents: { rows: 'results', title: 'title', subtitle: 'dateLine', image: 'image', view: 'grid', backendQuery: true, searchPlaceholder: '
|
|
53
|
+
himiEvents: { rows: 'results', title: 'title', subtitle: 'dateLine', image: 'image', view: 'grid', backendQuery: true, defaultFilter: 'value:Upcoming', searchPlaceholder: 'Search events', countNoun: ['event', 'events'], filterValues: ['Upcoming', 'Past'], sortLabels: { default: 'Date', az: 'Title A–Z', za: 'Title Z–A' }, primaryVerb: { label: 'Register' } },
|
|
47
54
|
himiBlogFeed: { rows: 'posts', title: 'title', subtitle: 'excerpt', image: 'image', filter: 'category', filterValuesFrom: 'categories', view: 'grid', narrowedBy: 'selectedCategoryId' },
|
|
48
|
-
himiOwnerLiteProducts: { rows: 'products', title: 'name', subtitle: 'price', image: 'image', view: 'grid', backendQuery: true },
|
|
55
|
+
himiOwnerLiteProducts: { rows: 'products', title: 'name', subtitle: 'price', image: 'image', view: 'grid', backendQuery: true, countNoun: ['product', 'products'] },
|
|
49
56
|
himiOwnerModules: { instanceInput: 'module', rows: 'rows', title: 'title', subtitle: 'subtitle', image: 'image', filter: 'statusLabel', view: 'list', paged: true, reviewAction: 'prepareRecord', reviewSelectedAction: 'prepareSelected' },
|
|
50
57
|
himiOwnerLiteServices: { rows: 'services', title: 'name', subtitle: 'subtitle', view: 'list', backendQuery: true, searchPlaceholder: 'Service name starts with…' },
|
|
51
58
|
himiOwnerLiteEvents: { rows: 'events', title: 'title', subtitle: 'dateLine', view: 'grid', backendQuery: true, searchPlaceholder: 'Exact event title', filterValues: ['Upcoming'], sortLabels: { default: 'Date', az: 'Title A–Z', za: 'Title Z–A' } },
|
|
52
59
|
himiOwnerLitePosts: { rows: 'posts', title: 'title', subtitle: 'excerpt', image: 'image', view: 'grid' },
|
|
53
|
-
himiOwnerLiteOrders: { rows: 'orders', title: 'buyer', subtitle: 'meta', filter: 'statusLabel', view: 'list', backendQuery: true,
|
|
60
|
+
himiOwnerLiteOrders: { rows: 'orders', title: 'buyer', subtitle: 'meta', filter: 'statusLabel', view: 'list', backendQuery: true, countNoun: ['order', 'orders'],
|
|
54
61
|
searchPlaceholder: 'Customer name, email or order #', sortLabels: { default: 'Newest', az: 'Customer A–Z', za: 'Customer Z–A' },
|
|
55
62
|
filterValues: ['Recent 7 days', 'Paid', 'Unpaid', 'Partially paid', 'Pending', 'Refunded', 'Partly refunded'] },
|
|
56
63
|
himiBrandedGroups: { rows: 'rows', title: 'name', subtitle: 'description', image: 'image', view: 'grid' },
|
|
57
64
|
himiBrandedCms: { rows: 'rows', title: 'name', subtitle: 'subtitle', view: 'grid' },
|
|
58
65
|
himiBrandedCmsCollection: { instanceInput: 'collection', rows: 'rows', title: 'title', subtitle: 'subtitle', view: 'list' },
|
|
59
66
|
himiBrandedOrders: { rows: 'rows', title: 'title', subtitle: 'subtitle', view: 'list', backendQuery: true,
|
|
60
|
-
searchPlaceholder: '
|
|
67
|
+
searchPlaceholder: 'Search by order number', countNoun: ['order', 'orders'], filterValues: ['Not fulfilled', 'Partially fulfilled', 'Fulfilled'],
|
|
61
68
|
sortLabels: { default: 'Newest', az: 'Oldest', za: 'Order # ↓' } },
|
|
62
|
-
himiBrandedPlans: { rows: 'rows', title: 'name', subtitle: 'description', metadata: ['priceLine', 'periodLine', 'trialBadge', 'perk1', 'perk2', 'perk3'], view: 'grid' },
|
|
69
|
+
himiBrandedPlans: { rows: 'rows', title: 'name', subtitle: 'description', metadata: ['badge', 'priceLine', 'periodLine', 'trialBadge', 'perk1', 'perk2', 'perk3'], view: 'grid', gridColumns: 1, metaMaxLines: 8, countNoun: ['plan', 'plans'] },
|
|
63
70
|
himiBrandedPrograms: { rows: 'rows', title: 'name', subtitle: 'description', image: 'image', view: 'grid' },
|
|
64
71
|
himiBrandedCoupons: { rows: 'coupons', title: 'name', subtitle: 'subtitle', view: 'list', primaryVerb: { label: 'Get code' } },
|
|
65
72
|
himiBrandedNotifications: { rows: 'items', title: 'name', subtitle: 'subtitle', image: 'photo', metadata: ['unreadDot'], view: 'list' },
|
|
@@ -75,7 +82,7 @@ export const COLLECTIONS: Record<string, CollectionConfig> = {
|
|
|
75
82
|
himiBrandedForms: { rows: 'forms', title: 'name', subtitle: 'description', view: 'list' },
|
|
76
83
|
himiBrandedGalleries: { rows: 'galleries', title: 'name', subtitle: 'description', image: 'image', view: 'grid' },
|
|
77
84
|
himiBrandedInbox: { rows: 'conversations', title: 'title', subtitle: 'preview', view: 'list' },
|
|
78
|
-
himiBrandedMenus: { rows: 'rows', title: 'name', subtitle: 'description', image: 'image', sectioned: true, filter: 'collectionCategory', metadata: ['collectionCategory', 'priceLine', 'label1', 'label2', 'label3'], view: 'grid' },
|
|
85
|
+
himiBrandedMenus: { rows: 'rows', title: 'name', subtitle: 'description', image: 'image', sectioned: true, filter: 'collectionCategory', metadata: ['collectionCategory', 'priceLine', 'label1', 'label2', 'label3'], view: 'grid', searchPlaceholder: 'Search the menu', countNoun: ['dish', 'dishes'] },
|
|
79
86
|
};
|
|
80
87
|
export interface CollectionPreferences {
|
|
81
88
|
view: 'list' | 'grid';
|
|
@@ -18,6 +18,20 @@ type Reducer = (s: State, ...args: unknown[]) => State | undefined | Promise<Sta
|
|
|
18
18
|
const object = (x: unknown): State => x && typeof x === 'object' && !Array.isArray(x) ? x as State : {};
|
|
19
19
|
const str = (arg: unknown, key: string): string => typeof arg === 'string' ? arg : String(object(arg)[key] ?? '');
|
|
20
20
|
|
|
21
|
+
/** The toolbar count line — "12 dishes", or "3 of 12 dishes" while a filter narrows the list.
|
|
22
|
+
*
|
|
23
|
+
* It used to read "12 / 12 loaded" and "2 / 2 results" ([branded-lite-qa] #3002): the numbers
|
|
24
|
+
* described the implementation (a client-side filter over a fetched page) rather than what the
|
|
25
|
+
* customer is looking at, and the slash-and-suffix wrapped onto two lines on web. When the
|
|
26
|
+
* backend total is unknown the count shown is simply what is shown; the "Load more" button is
|
|
27
|
+
* what says there is more. */
|
|
28
|
+
export function collectionSummary(o: { shown: number; loaded: number; total?: number; backend?: boolean; noun?: [string, string] }): string {
|
|
29
|
+
const [one, many] = o.noun ?? [copyParam('collection.itemOne', 'item'), copyParam('collection.itemMany', 'items')];
|
|
30
|
+
const whole = o.backend ? (typeof o.total === 'number' ? o.total : o.shown) : o.loaded;
|
|
31
|
+
const noun = (whole === 1 ? one : many);
|
|
32
|
+
return o.shown < whole ? `${o.shown} ${copyParam('collection.of', 'of')} ${whole} ${noun}` : `${o.shown} ${o.shown === 1 ? one : many}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
/** Opt-in decorator. Domain reducers remain authoritative; collection preferences
|
|
22
36
|
* survive reducers which replace their entire state, including async refreshes. */
|
|
23
37
|
export function decorateCollection(name: string, def: { state: State; actions?: Record<string, Reducer> }, saving?: CollectionSaving, backend?: BackendCollection) {
|
|
@@ -179,11 +193,11 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
179
193
|
filtersPresented, previewPresented: !!previewId, preview: projected.items.filter(row => row.collectionId === previewId),
|
|
180
194
|
persistenceLabel: persisted.available ? copyParam('collection.onDevice', 'Saved on this device') : copyParam('collection.sessionOnly', 'Choices last for this session'),
|
|
181
195
|
noEmpty: loading || errored || sourceEmpty || domainNarrowed || projected.items.length > 0,
|
|
182
|
-
summary: backend && config.backendQuery
|
|
183
|
-
?
|
|
184
|
-
|
|
185
|
-
:
|
|
186
|
-
|
|
196
|
+
summary: backend && config.backendQuery && s.fromCache
|
|
197
|
+
? copyParam('collection.cached', 'Saved offline · may be outdated')
|
|
198
|
+
: collectionSummary({ shown: projected.items.length, loaded: projected.loaded, backend: !!(backend && config.backendQuery),
|
|
199
|
+
total: typeof s.total === 'number' ? s.total : undefined, noun: config.countNoun
|
|
200
|
+
? [copyParam(`collection.${name}.countOne`, config.countNoun[0]), copyParam(`collection.${name}.countMany`, config.countNoun[1])] : undefined }),
|
|
187
201
|
selectedSummary: `${prefs.selectedIds.length} ${copyParam('collection.selected', 'selected')}`,
|
|
188
202
|
filterChoices,
|
|
189
203
|
chips: config.filterValues ? filterChoices.filter(choice => ['all', 'saved', prefs.filter].includes(choice.id)) : filterChoices,
|
|
@@ -352,3 +352,54 @@ export function compareDateDesc(a: string, b: string): number {
|
|
|
352
352
|
if (bBad) return -1;
|
|
353
353
|
return tb - ta;
|
|
354
354
|
}
|
|
355
|
+
|
|
356
|
+
// --- [branded-lite-qa] #3079: one pattern per fact ---------------------------------------
|
|
357
|
+
//
|
|
358
|
+
// Before these existed, four screens formatted the same money four ways — Shop "69.00 ₪" (Wix's
|
|
359
|
+
// pre-formatted string), Services "₪70" (0 decimals), Menu "₪32.00", Plans "$24" — and two
|
|
360
|
+
// screens formatted the same clock two ways (Events "9:00 PM-11:00 PM", Reserve "20:00").
|
|
361
|
+
// Customers read that as sloppiness, and a whole "₪70" beside "₪69.50" reads as a different
|
|
362
|
+
// kind of number. So: one money pattern, one clock, one range.
|
|
363
|
+
|
|
364
|
+
// Money uses `formatCurrency(amount, currency, "en")` — the currency's NATURAL decimals
|
|
365
|
+
// ("₪70.00", "¥500"). Passing 0 fraction digits for whole amounts is what made a list read
|
|
366
|
+
// "₪70 · ₪69.50".
|
|
367
|
+
|
|
368
|
+
const KNOWN_SYMBOLS = new Set(Object.values(CURRENCY_SYMBOL));
|
|
369
|
+
|
|
370
|
+
/** Wix hands some prices over PRE-formatted in the site's regional style — "69.00 ₪" with the
|
|
371
|
+
* symbol trailing, often wrapped in bidi marks. Reorder those to the house pattern ("₪69.00") so
|
|
372
|
+
* they match every price we format ourselves. Only an en-shaped number ("69.00", "1,234.50") with
|
|
373
|
+
* a known trailing symbol is reordered: a comma-decimal or space-grouped string ("69,00 €",
|
|
374
|
+
* "1 234,56 €") is kept as Wix wrote it, because half-converting it would be worse. Anything else
|
|
375
|
+
* ("Market price") is returned trimmed. The store SDK's product price applies the same rule. */
|
|
376
|
+
export function normalizeFormattedPrice(formatted: string): string {
|
|
377
|
+
const raw = (formatted || "").replace(/[]/g, "").trim();
|
|
378
|
+
const m = /^(-?)(\d{1,3}(?:,\d{3})*(?:\.\d+)?|\d+(?:\.\d+)?)[\s ]*(\S+)$/.exec(raw);
|
|
379
|
+
if (!m || !KNOWN_SYMBOLS.has(m[3])) return raw;
|
|
380
|
+
return `${m[1]}${m[3]}${m[2]}`;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function clockParts(d: Date, locale: string): { time: string; meridiem: string } {
|
|
384
|
+
const full = hasIntl()
|
|
385
|
+
? new Intl.DateTimeFormat(locale, { hour: "numeric", minute: "2-digit", hour12: true }).format(d)
|
|
386
|
+
: clock12(d);
|
|
387
|
+
// ICU puts a narrow no-break space before the meridiem; normalize so callers can split on it.
|
|
388
|
+
const clean = full.replace(/[ ]/g, " ");
|
|
389
|
+
const m = /^(.*?)\s*([AP]\.?M\.?)$/i.exec(clean);
|
|
390
|
+
return m ? { time: m[1], meridiem: m[2] } : { time: clean, meridiem: "" };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** "8:00 PM" — the one clock every customer screen uses (12-hour, en-US shape). */
|
|
394
|
+
export function formatClock(date: Date, locale = "en-US"): string {
|
|
395
|
+
const { time, meridiem } = clockParts(date, locale);
|
|
396
|
+
return meridiem ? `${time} ${meridiem}` : time;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** Is this ISO instant before `now`? A missing or unparseable value is NOT past — "no expiry"
|
|
400
|
+
* must never classify an offer as expired. */
|
|
401
|
+
export function isPastDate(iso: string | undefined | null, now: number = Date.now()): boolean {
|
|
402
|
+
if (!iso) return false;
|
|
403
|
+
const t = Date.parse(iso);
|
|
404
|
+
return !Number.isNaN(t) && t < now;
|
|
405
|
+
}
|
|
@@ -11,7 +11,7 @@ import { wixMediaUrl, type WixImageSize } from "@himalaya/wix-media";
|
|
|
11
11
|
// copier recognizes + carries the dependency; resolves to the same file here
|
|
12
12
|
// and in the scaffolded tier5-src/_shared/ layout.
|
|
13
13
|
import { cleanTitle, initials } from "../_shared/himi-text.js";
|
|
14
|
-
import { formatCurrency } from "../_shared/engine-intl.js";
|
|
14
|
+
import { formatCurrency, normalizeFormattedPrice } from "../_shared/engine-intl.js";
|
|
15
15
|
|
|
16
16
|
/** One dish tile/row: the media-XOR-initials card contract every himi list
|
|
17
17
|
* uses, plus a formatted price line and the templated detail screen id. */
|
|
@@ -97,13 +97,13 @@ export function itemPriceLine(item: WixItem): string {
|
|
|
97
97
|
| { price?: unknown; currency?: unknown; formattedPrice?: unknown }
|
|
98
98
|
| undefined;
|
|
99
99
|
const formatted = typeof info?.formattedPrice === "string" ? info.formattedPrice.trim() : "";
|
|
100
|
-
if (formatted) return formatted;
|
|
100
|
+
if (formatted) return normalizeFormattedPrice(formatted);
|
|
101
101
|
const price = info?.price;
|
|
102
102
|
if (price == null || price === "" || Number.isNaN(Number(price))) return "";
|
|
103
103
|
const currency = typeof info?.currency === "string" ? info.currency : "";
|
|
104
104
|
if (!currency) return `${price}`;
|
|
105
105
|
try {
|
|
106
|
-
return formatCurrency(Number(price), currency, "en"
|
|
106
|
+
return formatCurrency(Number(price), currency, "en");
|
|
107
107
|
} catch {
|
|
108
108
|
return `${price}`;
|
|
109
109
|
}
|
|
@@ -149,7 +149,7 @@ export function itemPriceVariants(item: WixItem): Array<{ id: string; name: stri
|
|
|
149
149
|
let priceLine = `${price}`;
|
|
150
150
|
if (currency) {
|
|
151
151
|
try {
|
|
152
|
-
priceLine = formatCurrency(Number(price), currency, "en"
|
|
152
|
+
priceLine = formatCurrency(Number(price), currency, "en");
|
|
153
153
|
} catch { /* keep the bare amount */ }
|
|
154
154
|
}
|
|
155
155
|
rows.push({ id: typeof node.id === "string" && node.id ? node.id : `variant-${index}`, name: name || "Option", priceLine });
|
|
@@ -74,6 +74,54 @@ const SITE_CONTEXT_CODES = ["MISSING_METASITE_ID", "MISSING_REQUEST_SITE_CONTEXT
|
|
|
74
74
|
* message and starts being a payload dump. */
|
|
75
75
|
const MAX_RAW = 200;
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* A TRANSPORT failure: the request never got an HTTP answer at all.
|
|
79
|
+
*
|
|
80
|
+
* Each host describes one in its own platform's words, and every one of them is a
|
|
81
|
+
* diagnostic dump rather than a message. iOS's JSWorker hands a worker the Swift
|
|
82
|
+
* description of the `NSError` —
|
|
83
|
+
* `Error Domain=NSURLErrorDomain Code=-1004 "Could not connect to the server."
|
|
84
|
+
* UserInfo={… NSErrorFailingURLStringKey=http://127.0.0.1:8178/__dev/wix/proxy, …}`
|
|
85
|
+
* — which Aria painted verbatim: the domain, the code, and the local server's
|
|
86
|
+
* own URL. Android's bridge carries the `java.net` exception text, and a browser's
|
|
87
|
+
* fetch rejects with "Failed to fetch" / "Load failed" / "NetworkError …".
|
|
88
|
+
*
|
|
89
|
+
* Only a message with NO HTTP status qualifies: a status means Wix answered, and
|
|
90
|
+
* that case keeps its own reductions below.
|
|
91
|
+
*/
|
|
92
|
+
const TRANSPORT = [
|
|
93
|
+
/\bError Domain=\w+/, // any Cocoa NSError description
|
|
94
|
+
/\bNSURLErrorDomain\b/,
|
|
95
|
+
/\bjava\.(?:net|io)\.\w+(?:Exception|Error)\b/,
|
|
96
|
+
/\b(?:UnknownHost|Connect|SocketTimeout|NoRouteToHost|SSLHandshake)Exception\b/,
|
|
97
|
+
// Android's WebViewJSWorker bridges `e.message ?: e.javaClass.simpleName`, so
|
|
98
|
+
// the class name is usually absent and only OkHttp's own message arrives.
|
|
99
|
+
/^Failed to connect to \S/,
|
|
100
|
+
/^Unable to resolve host\b/,
|
|
101
|
+
/\b(?:Connection refused|Network is unreachable|Software caused connection abort)\b/,
|
|
102
|
+
/^(?:timeout|Read timed out|connect timed out)$/i,
|
|
103
|
+
/\bFailed to fetch\b/,
|
|
104
|
+
/\bNetworkError when attempting to fetch\b/,
|
|
105
|
+
/^(?:TypeError: )?Load failed$/,
|
|
106
|
+
/\bnet::ERR_[A-Z_]+/,
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
/** Did this failure happen below HTTP — no answer from Wix at all? */
|
|
110
|
+
export function isTransportFailure(e: unknown): boolean {
|
|
111
|
+
const msg = String((e as Error)?.message ?? e ?? "");
|
|
112
|
+
if (STATUS.test(msg)) return false;
|
|
113
|
+
return TRANSPORT.some((re) => re.test(msg));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The one sentence for a transport failure. Copy-as-configuration: the key is
|
|
117
|
+
* under `ownerCommon.`, which every owner screen (Aria included) forwards. */
|
|
118
|
+
export function ownerTransportFailureText(): string {
|
|
119
|
+
return copyParam(
|
|
120
|
+
"ownerCommon.networkUnreachable",
|
|
121
|
+
"Couldn’t reach Wix. Check your connection and try again.",
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
77
125
|
/**
|
|
78
126
|
* Reduce a wire failure to something a PERSON can read.
|
|
79
127
|
*
|
|
@@ -85,6 +133,7 @@ const MAX_RAW = 200;
|
|
|
85
133
|
* markup; a long non-HTML body is truncated for the same reason.
|
|
86
134
|
*/
|
|
87
135
|
function humanWireError(msg: string): string {
|
|
136
|
+
if (isTransportFailure(msg)) return ownerTransportFailureText();
|
|
88
137
|
const status = STATUS.exec(msg)?.[1];
|
|
89
138
|
// A JSON ENVELOPE or a NAMED REASON CODE is the machine's word for it, and the
|
|
90
139
|
// owner gets neither. Both reductions used to live in `ownerDisplayError` only,
|
|
@@ -119,7 +119,7 @@ export function formatServicePrice(service: unknown): string {
|
|
|
119
119
|
if (amount == null) return "";
|
|
120
120
|
const currency = serviceCurrency(service);
|
|
121
121
|
try {
|
|
122
|
-
return formatCurrency(amount, currency, "en"
|
|
122
|
+
return formatCurrency(amount, currency, "en");
|
|
123
123
|
} catch {
|
|
124
124
|
return `${amount}`;
|
|
125
125
|
}
|
|
@@ -11,9 +11,14 @@ const input = (globalThis as { himalaya?: { input?: Record<string, unknown> } })
|
|
|
11
11
|
const validDate = (value: unknown): value is string => typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value) && dayKey(new Date(`${value}T12:00:00`)) === value;
|
|
12
12
|
const views = localPreferences("booking.schedule", value => ["agenda", "day", "month"].includes(String(value)) ? String(value) : "agenda");
|
|
13
13
|
let mode = validDate(input?.date) ? "day" : views.read();
|
|
14
|
-
|
|
14
|
+
// "" until the schedule is first shown, NOT today's date: the release build hashes this worker's
|
|
15
|
+
// initial state, so a date read off the build machine's clock made every build of unchanged
|
|
16
|
+
// source a different release (#3268, the `nondeterministic-initial-state` lint). `derive()` fills
|
|
17
|
+
// in today the first time the schedule is visible, which is never at build time (no member).
|
|
18
|
+
let selectedDate = validDate(input?.date) ? input.date : "";
|
|
19
|
+
const today = () => dayKey(new Date());
|
|
15
20
|
let pageMember: MemberAccess | undefined;
|
|
16
|
-
let queryNow =
|
|
21
|
+
let queryNow = ""; // set by the first-page fetch, never at boot (see selectedDate)
|
|
17
22
|
let cancelTarget: { row: MemberBookingRow; member: MemberAccess } | undefined;
|
|
18
23
|
let cancelling = false;
|
|
19
24
|
interface S {
|
|
@@ -26,11 +31,12 @@ interface S {
|
|
|
26
31
|
nextCursor?: string; complete?: boolean; loadingMore?: boolean; pageError?: boolean; total?: number;
|
|
27
32
|
}
|
|
28
33
|
function derive(s: S): S {
|
|
29
|
-
const
|
|
34
|
+
const hideSchedule = !s.isMember || s.isLoading;
|
|
35
|
+
if (!selectedDate && !hideSchedule) selectedDate = today();
|
|
36
|
+
const dayRows = selectedDate ? s.rows.filter(row => row.day === selectedDate) : [];
|
|
30
37
|
const counts = new Map<string, number>();
|
|
31
38
|
for (const row of s.rows) if (row.day) counts.set(row.day, (counts.get(row.day) ?? 0) + 1);
|
|
32
|
-
|
|
33
|
-
return { ...s, scheduleMode: mode, selectedDate, dayRows, dayHeading: dayLabel(selectedDate), hideSchedule,
|
|
39
|
+
return { ...s, scheduleMode: mode, selectedDate, dayRows, dayHeading: selectedDate ? dayLabel(selectedDate) : "", hideSchedule,
|
|
34
40
|
hideAgenda: hideSchedule || mode !== "agenda", hideCalendar: hideSchedule || mode === "agenda", hideMonth: mode !== "month",
|
|
35
41
|
decorations: [...counts].map(([date, n]) => ({ date, dotCount: Math.min(n, 3), dotColorToken: "accentPrimary" })),
|
|
36
42
|
dayEmptyText: dayRows.length ? "" : s.complete ? copyParam("bookings.emptyDay", "No bookings on this day.") : copyParam("bookings.emptyLoadedDay", "No bookings on this day in loaded results. Load more to check later pages."),
|
|
@@ -48,7 +54,7 @@ const svc = defineBackendCollection<S, MemberBookingRow>("himiBookingBookings",
|
|
|
48
54
|
async fetch(criteria, cursor) {
|
|
49
55
|
const member = await memberAccess();
|
|
50
56
|
if (cursor && (!pageMember || pageMember.email !== member.email || pageMember.scope !== member.scope)) throw memberAccessChanged();
|
|
51
|
-
if (!cursor) queryNow = new Date().toISOString();
|
|
57
|
+
if (!cursor || !queryNow) queryNow = new Date().toISOString();
|
|
52
58
|
return fetchMemberBookings(criteria, cursor, queryNow, member);
|
|
53
59
|
},
|
|
54
60
|
},
|
|
@@ -65,8 +71,8 @@ const svc = defineBackendCollection<S, MemberBookingRow>("himiBookingBookings",
|
|
|
65
71
|
actions: {
|
|
66
72
|
scheduleView(s, arg) { const value = (arg as { selectedId?: string })?.selectedId; if (["agenda", "day", "month"].includes(value || "")) { mode = value!; views.write(mode); } return derive(s); },
|
|
67
73
|
selectDate(s, arg) { const date = (arg as { date?: unknown })?.date; if (validDate(date)) selectedDate = date; return derive(s); },
|
|
68
|
-
previousDay(s) { const date = new Date(`${selectedDate}T12:00:00`); date.setDate(date.getDate() - 1); selectedDate = dayKey(date); return derive(s); },
|
|
69
|
-
nextDay(s) { const date = new Date(`${selectedDate}T12:00:00`); date.setDate(date.getDate() + 1); selectedDate = dayKey(date); return derive(s); },
|
|
74
|
+
previousDay(s) { const date = new Date(`${selectedDate || today()}T12:00:00`); date.setDate(date.getDate() - 1); selectedDate = dayKey(date); return derive(s); },
|
|
75
|
+
nextDay(s) { const date = new Date(`${selectedDate || today()}T12:00:00`); date.setDate(date.getDate() + 1); selectedDate = dayKey(date); return derive(s); },
|
|
70
76
|
async rebook(s, arg) {
|
|
71
77
|
const member = pageMember, row = s.rows.find(row => row.id === (arg as { id?: string })?.id);
|
|
72
78
|
if (!member || !row?.serviceId) return s;
|
|
@@ -38,14 +38,14 @@ interface State {
|
|
|
38
38
|
|
|
39
39
|
function row(collection: CmsCollectionDefinition): CollectionRow {
|
|
40
40
|
const title = collection.displayName.trim() || collection.id;
|
|
41
|
-
|
|
41
|
+
// No field count and no "Available to visitors": both are back-office schema/permission
|
|
42
|
+
// metadata, meaningless to the visitor reading the card ([branded-lite-qa] #3077). "Members
|
|
43
|
+
// only" stays — it tells the visitor they will need to sign in, which is theirs to know.
|
|
42
44
|
return {
|
|
43
45
|
id: collection.id,
|
|
44
46
|
title,
|
|
45
|
-
subtitle:
|
|
46
|
-
meta: collection.readRole === "ANYONE"
|
|
47
|
-
? copyParam("cms.visitorAccessLabel", "Available to visitors")
|
|
48
|
-
: copyParam("cms.membersOnlyAccessLabel", "Members only"),
|
|
47
|
+
subtitle: "",
|
|
48
|
+
meta: collection.readRole === "ANYONE" ? "" : copyParam("cms.membersOnlyAccessLabel", "Members only"),
|
|
49
49
|
initial: (title[0] || "C").toUpperCase(),
|
|
50
50
|
detailScreenId: `himi_cms_collection_${encodeURIComponent(collection.id)}`,
|
|
51
51
|
};
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
import { defineService } from "../_shared/collection-service.js";
|
|
7
7
|
import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
|
|
8
8
|
import { queryCoupons, type WixCoupon } from "@himalaya/wix-coupons";
|
|
9
|
+
import { copyParam, copyTemplate } from "../_shared/himi-copy.js";
|
|
10
|
+
import { isPastDate, formatMonthDay } from "../_shared/engine-intl.js";
|
|
9
11
|
|
|
10
12
|
interface Row { id: string; name: string; subtitle: string }
|
|
11
13
|
interface S extends LoadFlags {
|
|
@@ -17,16 +19,40 @@ interface S extends LoadFlags {
|
|
|
17
19
|
notMockEmpty: boolean; notMockUnavailable: boolean;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
/** Rows from the live Coupons API. The query returns expired coupons alongside live ones, and the
|
|
23
|
+
* row used to show them identically — an offer that "ended Aug 31" still listed as available in
|
|
24
|
+
* September. An expired row now says so and sinks below the live ones;
|
|
25
|
+
* a live one with an end date says when it ends. Both the flat and the `specification` shape are
|
|
26
|
+
* read, because the stores v2 payload nests the name/code/expiry under `specification`. */
|
|
27
|
+
function norm(c: WixCoupon, now: number): Row & { expired: boolean } {
|
|
28
|
+
const cc = c as unknown as {
|
|
29
|
+
id?: string; name?: string; code?: string; expirationTime?: string; expired?: boolean;
|
|
30
|
+
specification?: { name?: string; code?: string; expirationTime?: string };
|
|
31
|
+
};
|
|
32
|
+
const code = cc.code || cc.specification?.code || "";
|
|
33
|
+
const name = cc.name || cc.specification?.name || code || "Coupon";
|
|
34
|
+
const ends = cc.expirationTime || cc.specification?.expirationTime || "";
|
|
35
|
+
const expired = cc.expired === true || isPastDate(ends, now);
|
|
36
|
+
const endLabel = ends && !Number.isNaN(Date.parse(ends))
|
|
37
|
+
? copyTemplate("coupons.endsLabel", "ends {date}", { date: formatMonthDay(new Date(ends)) }) : "";
|
|
38
|
+
const status = expired ? copyParam("coupons.expiredLabel", "Expired") : endLabel;
|
|
39
|
+
const subtitle = [code ? copyTemplate("coupons.codeLabel", "Code: {code}", { code }) : "", status].filter(Boolean).join(" · ");
|
|
40
|
+
return { id: cc.id || code || "coupon", name, subtitle, expired };
|
|
23
41
|
}
|
|
24
42
|
|
|
43
|
+
/** Live offers first, expired ones after — stable within each group. */
|
|
44
|
+
export function couponRows(coupons: WixCoupon[], now: number = Date.now()): Row[] {
|
|
45
|
+
const rows = coupons.map((c) => norm(c, now));
|
|
46
|
+
return [...rows.filter((r) => !r.expired), ...rows.filter((r) => r.expired)].map(({ expired: _e, ...row }) => row);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Sample rows carry no dates and no currency: a hardcoded "ends Aug 31" went stale the day after,
|
|
50
|
+
// and "$10 off" read as the wrong currency on a ₪ site.
|
|
25
51
|
const MOCK: Row[] = [
|
|
26
|
-
{ id: "sample-welcome15", name: "Welcome — 15% off your first order", subtitle: "Code: WELCOME15
|
|
27
|
-
{ id: "sample-freeship", name: "Free shipping", subtitle: "Code: FREESHIP ·
|
|
52
|
+
{ id: "sample-welcome15", name: "Welcome — 15% off your first order", subtitle: "Code: WELCOME15" },
|
|
53
|
+
{ id: "sample-freeship", name: "Free shipping", subtitle: "Code: FREESHIP · on eligible orders" },
|
|
28
54
|
{ id: "sample-flash20", name: "Weekend flash — 20% off", subtitle: "Code: FLASH20 · this weekend only" },
|
|
29
|
-
{ id: "sample-loyal10", name: "Loyalty reward —
|
|
55
|
+
{ id: "sample-loyal10", name: "Loyalty reward — 10% off", subtitle: "Code: LOYAL10 · members only" },
|
|
30
56
|
];
|
|
31
57
|
|
|
32
58
|
/** sample rows reach the screen ONLY in the host's explicit sample preview;
|
|
@@ -45,7 +71,7 @@ const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedCoupons
|
|
|
45
71
|
load: async (s) => {
|
|
46
72
|
try {
|
|
47
73
|
const r = await queryCoupons({ paging: { limit: 30 } } as never);
|
|
48
|
-
const coupons = (((r as unknown as { coupons?: WixCoupon[] }).coupons) ?? [])
|
|
74
|
+
const coupons = couponRows(((r as unknown as { coupons?: WixCoupon[] }).coupons) ?? []);
|
|
49
75
|
return { ...s, ...loaded("coupons", coupons, COPY) };
|
|
50
76
|
} catch (err) {
|
|
51
77
|
return { ...s, ...loadFailed("coupons", err, COPY) };
|
|
@@ -34,9 +34,9 @@ function norm(c: WixDonationCampaign): Row {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const MOCK: Row[] = [
|
|
37
|
-
{ id: "sample-reef", title: "Save the Coral Reef", subtitle: "
|
|
38
|
-
{ id: "sample-school", title: "Build a Village School", subtitle: "
|
|
39
|
-
{ id: "sample-relief", title: "Winter Relief Fund", subtitle: "Goal
|
|
37
|
+
{ id: "sample-reef", title: "Save the Coral Reef", subtitle: "Active" },
|
|
38
|
+
{ id: "sample-school", title: "Build a Village School", subtitle: "Active" },
|
|
39
|
+
{ id: "sample-relief", title: "Winter Relief Fund", subtitle: "Goal reached" },
|
|
40
40
|
];
|
|
41
41
|
|
|
42
42
|
/** sample rows reach the screen ONLY in the host's explicit sample preview;
|
|
@@ -19,25 +19,24 @@ interface S extends LoadFlags {
|
|
|
19
19
|
notMockEmpty: boolean; notMockUnavailable: boolean;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const STATUS_LABEL: Record<string, string> = { PUBLISHED: "Published", DRAFT: "Draft" };
|
|
23
|
-
|
|
24
22
|
/** The Wix Forms namespace the Forms app owns. Booking/event registration forms live
|
|
25
23
|
* in their own namespaces and are not this screen's subject. */
|
|
26
24
|
const FORMS_NAMESPACE = "wix.form_app.form";
|
|
27
25
|
|
|
28
26
|
function norm(f: WixForm): Row {
|
|
29
27
|
const title = (f.name || "Form").trim() || "Form";
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
// The subtitle used to be "5 fields · Published" — a form-builder metric and a publishing
|
|
29
|
+
// status, both back-office facts ([branded-lite-qa] #3077). A visitor sees the form's own
|
|
30
|
+
// description when it has one, and nothing otherwise.
|
|
31
|
+
const raw = (f as { description?: unknown }).description;
|
|
32
|
+
return { id: f.id || "form", title, subtitle: typeof raw === "string" ? raw.trim() : "" };
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
/** Preview-only sample forms (see COPY below). */
|
|
37
36
|
const MOCK: Row[] = [
|
|
38
|
-
{ id: "sample-contact", title: "Contact Us", subtitle: "
|
|
39
|
-
{ id: "sample-subscribe", title: "Newsletter Signup", subtitle: "
|
|
40
|
-
{ id: "sample-feedback", title: "Feedback", subtitle: "
|
|
37
|
+
{ id: "sample-contact", title: "Contact Us", subtitle: "Send us a message." },
|
|
38
|
+
{ id: "sample-subscribe", title: "Newsletter Signup", subtitle: "Get news and offers by email." },
|
|
39
|
+
{ id: "sample-feedback", title: "Feedback", subtitle: "Tell us how we did." },
|
|
41
40
|
];
|
|
42
41
|
|
|
43
42
|
/** sample rows reach the screen ONLY in the host's explicit sample preview;
|
|
@@ -56,7 +55,9 @@ const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedForms",
|
|
|
56
55
|
load: async (s) => {
|
|
57
56
|
try {
|
|
58
57
|
const r = await queryForms({ filter: { namespace: FORMS_NAMESPACE }, paging: { limit: 50 } });
|
|
59
|
-
|
|
58
|
+
// A visitor can only fill a live form: a disabled or draft one used to be listed with a
|
|
59
|
+
// "Disabled"/"Draft" label; now that the row carries no back-office status, it is not listed.
|
|
60
|
+
const forms = (r.forms ?? []).filter((f) => f.enabled !== false && !/^DRAFT$/i.test(String(f.status ?? ""))).map(norm);
|
|
60
61
|
return { ...s, ...loaded("forms", forms, COPY) };
|
|
61
62
|
} catch (err) {
|
|
62
63
|
return { ...s, ...loadFailed("forms", err, COPY) };
|
|
@@ -38,9 +38,9 @@ function norm(c: WixGiftCard): Row {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
const MOCK: Row[] = [
|
|
41
|
-
{ id: "sample-1", title: "Gift card ····4821", subtitle: "
|
|
42
|
-
{ id: "sample-2", title: "Gift card ····9075", subtitle: "
|
|
43
|
-
{ id: "sample-3", title: "Gift card ····3312", subtitle: "
|
|
41
|
+
{ id: "sample-1", title: "Gift card ····4821", subtitle: "Example gift card" },
|
|
42
|
+
{ id: "sample-2", title: "Gift card ····9075", subtitle: "Example gift card" },
|
|
43
|
+
{ id: "sample-3", title: "Gift card ····3312", subtitle: "Example gift card" },
|
|
44
44
|
];
|
|
45
45
|
|
|
46
46
|
/** sample rows reach the screen ONLY in the host's explicit sample preview;
|
|
@@ -388,6 +388,10 @@ function resolveEffectiveLayout(arg: unknown, productCount: number): LayoutId {
|
|
|
388
388
|
});
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
/** The time-of-day greeting. Read at REFRESH time only, never at boot: the release build hashes
|
|
392
|
+
* the initial state, so a greeting read off the build machine's clock made every build of an
|
|
393
|
+
* unchanged template a different release (#3268, the `nondeterministic-initial-state` lint).
|
|
394
|
+
* The initial state carries `neutralWelcome()` instead, and the first refresh swaps this in. */
|
|
391
395
|
function welcome(): string {
|
|
392
396
|
const hour = new Date().getHours();
|
|
393
397
|
if (hour < 12) return copyParam("home.greetingMorning", "Good morning");
|
|
@@ -395,6 +399,11 @@ function welcome(): string {
|
|
|
395
399
|
return copyParam("home.greetingEvening", "Good evening");
|
|
396
400
|
}
|
|
397
401
|
|
|
402
|
+
/** The boot-time greeting: clock-free, so the baked initial state is the same on every build. */
|
|
403
|
+
function neutralWelcome(): string {
|
|
404
|
+
return copyParam("home.greetingNeutral", "Welcome");
|
|
405
|
+
}
|
|
406
|
+
|
|
398
407
|
function feature(p: StoreProduct): ProductFeature {
|
|
399
408
|
const name = cleanTitle(p.name) || copyParam("home.productFallbackName", "Featured item");
|
|
400
409
|
const image = productImage(p);
|
|
@@ -871,7 +880,9 @@ function baseState(): State {
|
|
|
871
880
|
hideHeroInitial: true,
|
|
872
881
|
hideHeroFallback: true,
|
|
873
882
|
hideFeaturedSkeleton: false,
|
|
874
|
-
|
|
883
|
+
// Not welcome(): that reads the clock, and this state is baked into the release. A neutral
|
|
884
|
+
// greeting keeps the hero pill painted (same size, no reflow) until the first refresh.
|
|
885
|
+
welcome: neutralWelcome(),
|
|
875
886
|
refreshedAt: "",
|
|
876
887
|
effectiveLayout: resolveEffectiveLayout(undefined, 0),
|
|
877
888
|
accentColor: resolveAccent(undefined),
|
|
@@ -112,7 +112,10 @@ const svc = defineService<State, {
|
|
|
112
112
|
// Probe before revealing anything picker-shaped (spec R8) — on a customer build this
|
|
113
113
|
// stays false and the whole subtree is hidden.
|
|
114
114
|
const canPick = await pickerAvailable();
|
|
115
|
-
|
|
115
|
+
// The runtime line names the internal platform ("HimalayaCore 0.0.1") — a number for
|
|
116
|
+
// whoever debugs the build, not for a white-label app's customer ([branded-lite-qa]
|
|
117
|
+
// #3002/#3083). It shows only on a build that opted into the QA picker.
|
|
118
|
+
next = { ...next, pickerAvailable: canPick, pickerHidden: !canPick, runtimeLine: canPick ? next.runtimeLine : "" };
|
|
116
119
|
if (canPick) {
|
|
117
120
|
// `active()` is more precise than app-info's releasePinned: it also says WHICH pin
|
|
118
121
|
// (user vs baked) is holding the release.
|
|
@@ -122,7 +125,7 @@ const svc = defineService<State, {
|
|
|
122
125
|
}
|
|
123
126
|
}
|
|
124
127
|
} catch {
|
|
125
|
-
next = { ...next, pickerAvailable: false, pickerHidden: true };
|
|
128
|
+
next = { ...next, pickerAvailable: false, pickerHidden: true, runtimeLine: "" };
|
|
126
129
|
}
|
|
127
130
|
return next;
|
|
128
131
|
},
|
|
@@ -39,9 +39,9 @@ function norm(v: WixInvoice): Row {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const MOCK: Row[] = [
|
|
42
|
-
{ id: "sample-1024", title: "Invoice #1024", subtitle: "Paid
|
|
43
|
-
{ id: "sample-1031", title: "Invoice #1031", subtitle: "Sent
|
|
44
|
-
{ id: "sample-1042", title: "Invoice #1042", subtitle: "Overdue
|
|
42
|
+
{ id: "sample-1024", title: "Invoice #1024", subtitle: "Paid" },
|
|
43
|
+
{ id: "sample-1031", title: "Invoice #1031", subtitle: "Sent" },
|
|
44
|
+
{ id: "sample-1042", title: "Invoice #1042", subtitle: "Overdue" },
|
|
45
45
|
];
|
|
46
46
|
|
|
47
47
|
/** sample rows reach the screen ONLY in the host's explicit sample preview;
|
|
@@ -31,7 +31,7 @@ function norm(r: WixLoyaltyReward, unit: string): Row {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const MOCK: Row[] = [
|
|
34
|
-
{ id: "sample-5off", name: "
|
|
34
|
+
{ id: "sample-5off", name: "5% off your next order", subtitle: "500 points" },
|
|
35
35
|
{ id: "sample-ship", name: "Free shipping", subtitle: "300 points" },
|
|
36
36
|
{ id: "sample-15", name: "15% off any purchase", subtitle: "750 points" },
|
|
37
37
|
{ id: "sample-gift", name: "Free welcome gift", subtitle: "1,000 points" },
|
|
@@ -78,7 +78,7 @@ function money(plan: WixPlan): string {
|
|
|
78
78
|
const raw = price?.value;
|
|
79
79
|
if (raw == null || raw === "" || Number.isNaN(Number(raw))) return "";
|
|
80
80
|
try {
|
|
81
|
-
return formatCurrency(Number(raw), price?.currency || "USD", "en"
|
|
81
|
+
return formatCurrency(Number(raw), price?.currency || "USD", "en");
|
|
82
82
|
} catch {
|
|
83
83
|
return `${raw}`;
|
|
84
84
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// himi-branded-lite-portfolio — real, public Wix Portfolio projects.
|
|
2
2
|
// Empty and error states stay explicit; media IDs are normalized for every host.
|
|
3
3
|
import { defineService } from "../_shared/collection-service.js";
|
|
4
|
-
import { listProjects, type WixPortfolioProject } from "@himalaya/wix-portfolio";
|
|
4
|
+
import { listProjects, portfolioCoverRef, type WixPortfolioProject } from "@himalaya/wix-portfolio";
|
|
5
5
|
import { wixMediaUrl } from "@himalaya/wix-media";
|
|
6
6
|
|
|
7
7
|
interface Row {
|
|
@@ -24,7 +24,7 @@ interface S {
|
|
|
24
24
|
|
|
25
25
|
function norm(project: WixPortfolioProject): Row {
|
|
26
26
|
const title = (project.title || "Project").trim() || "Project";
|
|
27
|
-
const image = wixMediaUrl(project.coverImage
|
|
27
|
+
const image = wixMediaUrl(portfolioCoverRef(project.coverImage));
|
|
28
28
|
return {
|
|
29
29
|
id: project.id || project.slug || title,
|
|
30
30
|
title,
|