cloudcommerce 0.20.2 → 0.22.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/.gitmodules +3 -0
- package/.nvmrc +1 -0
- package/.vscode/settings.json +2 -1
- package/CHANGELOG.md +44 -0
- package/action.yml +37 -13
- package/ecomplus-stores/iluminim/.devcontainer/devcontainer.json +30 -0
- package/ecomplus-stores/iluminim/.eslintrc.cjs +3 -0
- package/ecomplus-stores/iluminim/.firebaserc +5 -0
- package/ecomplus-stores/iluminim/.github/renovate.json +5 -0
- package/ecomplus-stores/iluminim/.github/workflows/build-and-deploy.yml +36 -0
- package/ecomplus-stores/iluminim/.github/workflows/calibreapp-image-actions.yml +23 -0
- package/ecomplus-stores/iluminim/.gitpod.yml +12 -0
- package/ecomplus-stores/iluminim/.nvmrc +1 -0
- package/ecomplus-stores/iluminim/.vscode/extensions.json +8 -0
- package/ecomplus-stores/iluminim/.vscode/launch.json +11 -0
- package/ecomplus-stores/iluminim/.vscode/settings.json +10 -0
- package/ecomplus-stores/iluminim/README.md +113 -0
- package/ecomplus-stores/iluminim/README.pt-BR.md +113 -0
- package/ecomplus-stores/iluminim/functions/config.json +3 -0
- package/ecomplus-stores/iluminim/functions/example.env +10 -0
- package/ecomplus-stores/iluminim/functions/many/index.js +14 -0
- package/ecomplus-stores/iluminim/functions/many/package.json +22 -0
- package/ecomplus-stores/iluminim/functions/ssr/.eslintrc.cjs +6 -0
- package/ecomplus-stores/iluminim/functions/ssr/astro.config.mjs +4 -0
- package/ecomplus-stores/iluminim/functions/ssr/content/blog/.gitkeep +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/content/extra-pages/.gitkeep +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/content/layout.json +33 -0
- package/ecomplus-stores/iluminim/functions/ssr/content/pages/home.json +70 -0
- package/ecomplus-stores/iluminim/functions/ssr/content/settings.json +49 -0
- package/ecomplus-stores/iluminim/functions/ssr/index.js +18 -0
- package/ecomplus-stores/iluminim/functions/ssr/package.json +31 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/admin/.gitkeep +2 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/assets/cms-preview.css +274 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/assets/cms.css +114 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/assets/cvv.png +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/assets/img-placeholder.png +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/assets/payments.png +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/assets/ssl-safe.png +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/img/icon.png +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/img/large-icon.png +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/img/uploads/banner-chamada-desktop-9x81zmd91q.webp +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/img/uploads/banner2.webp +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/img/uploads/chamada-mobile-q1c6om6jx4.webp +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/img/uploads/ecom-icon.png +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/img/uploads/headphone.webp +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/img/uploads/logo.webp +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/img/uploads/og-image.png +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/img/uploads/passion.webp +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/img/uploads/rect8589.png +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/img/uploads/rect859.png +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/img/uploads/rect89.webp +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/public/robots.txt +8 -0
- package/ecomplus-stores/iluminim/functions/ssr/scripts/build.sh +14 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/assets/style.css +65 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/AccountMenu.vue +104 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/Banner.vue +59 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/BannersGrid.astro +25 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/CartSidebar.vue +35 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/Countdown.vue +79 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/HeroSlider.vue +52 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/PitchBar.vue +57 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/Prices.vue +96 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/ProductCard.vue +118 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/ProductShelf.vue +60 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/ProductsCountdown.vue +20 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/SearchModal.vue +6 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/ShopHeader.vue +137 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/ShopHeaderMenu.vue +58 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/ShopHeaderSubmenu.vue +88 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/ShopSidenav.vue +61 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/components/ShopSidenavCategory.vue +78 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/env.d.ts +13 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/layouts/Base.astro +16 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/layouts/Checkout.astro +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/layouts/PageHeader.astro +33 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/main/Fallback.astro +10 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/main/Home.astro +49 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/main/Sections.astro +42 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/main/Wildcard.astro +18 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/pages/[...slug].astro +40 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/pages/_vue.ts +3 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/pages/app/account.astro +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/pages/app/index.astro +0 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/pages/fallback.astro +25 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/pages/index.astro +35 -0
- package/ecomplus-stores/iluminim/functions/ssr/src/scripts/InlineScripts.astro +10 -0
- package/ecomplus-stores/iluminim/functions/ssr/tailwind.config.cjs +13 -0
- package/ecomplus-stores/iluminim/functions/ssr/tsconfig.json +12 -0
- package/ecomplus-stores/iluminim/functions/ssr/uno.config.cjs +5 -0
- package/ecomplus-stores/iluminim/functions/with-apps/index.js +12 -0
- package/ecomplus-stores/iluminim/functions/with-apps/package.json +22 -0
- package/ecomplus-stores/iluminim/package.json +28 -0
- package/ecomplus-stores/iluminim/scripts/install.sh +14 -0
- package/ecomplus-stores/monocard/.eslintrc.cjs +3 -0
- package/ecomplus-stores/monocard/.nvmrc +1 -0
- package/ecomplus-stores/tia-sonia/.eslintrc.cjs +3 -0
- package/ecomplus-stores/tia-sonia/.nvmrc +1 -0
- package/package.json +4 -4
- package/packages/api/package.json +1 -1
- package/packages/api/types.d.ts +1 -1
- package/packages/apps/affilate-program/package.json +1 -1
- package/packages/apps/correios/package.json +1 -1
- package/packages/apps/custom-payment/package.json +1 -1
- package/packages/apps/custom-shipping/package.json +1 -1
- package/packages/apps/datafrete/package.json +1 -1
- package/packages/apps/discounts/package.json +1 -1
- package/packages/apps/emails/package.json +1 -1
- package/packages/apps/fb-conversions/package.json +1 -1
- package/packages/apps/flash-courier/package.json +1 -1
- package/packages/apps/frenet/package.json +1 -1
- package/packages/apps/galaxpay/package.json +1 -1
- package/packages/apps/google-analytics/package.json +1 -1
- package/packages/apps/jadlog/package.json +1 -1
- package/packages/apps/loyalty-points/package.json +1 -1
- package/packages/apps/melhor-envio/package.json +1 -1
- package/packages/apps/mercadopago/package.json +1 -1
- package/packages/apps/pagarme/package.json +1 -1
- package/packages/apps/paghiper/package.json +1 -1
- package/packages/apps/pix/package.json +1 -1
- package/packages/apps/tiny-erp/package.json +1 -1
- package/packages/apps/webhooks/package.json +1 -1
- package/packages/cli/config/firebase.json +10 -0
- package/packages/cli/package.json +1 -1
- package/packages/config/package.json +1 -1
- package/packages/emails/package.json +2 -2
- package/packages/events/package.json +1 -1
- package/packages/feeds/package.json +1 -1
- package/packages/firebase/package.json +1 -1
- package/packages/i18n/lib/en_us/i19hour.txt +1 -0
- package/packages/i18n/lib/en_us/i19hours.txt +1 -0
- package/packages/i18n/lib/en_us/i19milliseconds.txt +1 -0
- package/packages/i18n/lib/en_us/i19minute.txt +1 -0
- package/packages/i18n/lib/en_us/i19minutes.txt +1 -0
- package/packages/i18n/lib/en_us/i19second.txt +1 -0
- package/packages/i18n/lib/en_us/i19seconds.txt +1 -0
- package/packages/i18n/lib/en_us.d.ts +7 -0
- package/packages/i18n/lib/en_us.js +7 -0
- package/packages/i18n/lib/en_us.js.map +1 -1
- package/packages/i18n/lib/pt_br/i19home.txt +1 -1
- package/packages/i18n/lib/pt_br/i19hour.txt +1 -0
- package/packages/i18n/lib/pt_br/i19hours.txt +1 -0
- package/packages/i18n/lib/pt_br/i19milliseconds.txt +1 -0
- package/packages/i18n/lib/pt_br/i19minute.txt +1 -0
- package/packages/i18n/lib/pt_br/i19minutes.txt +1 -0
- package/packages/i18n/lib/pt_br/i19second.txt +1 -0
- package/packages/i18n/lib/pt_br/i19seconds.txt +1 -0
- package/packages/i18n/lib/pt_br.d.ts +8 -1
- package/packages/i18n/lib/pt_br.js +8 -1
- package/packages/i18n/lib/pt_br.js.map +1 -1
- package/packages/i18n/package.json +1 -1
- package/packages/i18n/src/en_us.ts +7 -0
- package/packages/i18n/src/pt_br.ts +8 -1
- package/packages/modules/package.json +1 -1
- package/packages/passport/package.json +1 -1
- package/packages/ssr/lib/firebase/serve-storefront.js +24 -18
- package/packages/ssr/lib/firebase/serve-storefront.js.map +1 -1
- package/packages/ssr/package.json +2 -2
- package/packages/ssr/src/firebase/serve-storefront.ts +25 -18
- package/packages/storefront/.eslintrc.cjs +8 -1
- package/packages/storefront/client.d.ts +1 -19
- package/packages/storefront/dist/client/_astro/HeroSlider.0890631f.js +1 -0
- package/packages/storefront/dist/client/_astro/{ShopHeader.0c884bbe.js → ShopHeader.82ae97a5.js} +1 -1
- package/packages/storefront/dist/client/_astro/_...slug_.c85b8978.css +1 -0
- package/packages/storefront/dist/client/_astro/{client.8035a95a.js → client.0fb6b44e.js} +1 -1
- package/packages/storefront/dist/client/_astro/{hoisted.9d9d7ac4.js → hoisted.572313d6.js} +1 -1
- package/packages/storefront/dist/client/sw.js +1 -1
- package/packages/storefront/dist/server/chunks/_...7e1df40a.mjs +34 -0
- package/packages/storefront/dist/server/chunks/{account@_@astro.89e4cf60.mjs → account@_@astro.5f1dba7a.mjs} +7 -8
- package/packages/storefront/dist/server/chunks/{astro.45d3047f.mjs → astro.2d3ebf0f.mjs} +560 -511
- package/packages/storefront/dist/server/chunks/{endpoint@_@js.f880a152.mjs → endpoint@_@js.80de0568.mjs} +7 -8
- package/packages/storefront/dist/server/chunks/{fallback@_@astro.89538855.mjs → fallback@_@astro.02a45b47.mjs} +7 -8
- package/packages/storefront/dist/server/chunks/{index@_@astro.56fdd6da.mjs → index@_@astro.23b50301.mjs} +7 -8
- package/packages/storefront/dist/server/chunks/{index@_@astro.548cf7ad.mjs → index@_@astro.7cb07e52.mjs} +7 -8
- package/packages/storefront/dist/server/chunks/pages/{_...slug_.astro.bde9380b.mjs → _...slug_.astro.4358f614.mjs} +92 -104
- package/packages/storefront/dist/server/chunks/pages/{account.astro.f07cf19e.mjs → account.astro.afae5a12.mjs} +6 -6
- package/packages/storefront/dist/server/chunks/pages/{endpoint.js.d4e37801.mjs → endpoint.js.60fd21aa.mjs} +1 -1
- package/packages/storefront/dist/server/chunks/pages/{fallback.astro.e2e8a0e4.mjs → fallback.astro.e5566925.mjs} +10 -10
- package/packages/storefront/dist/server/chunks/pages/{index.astro.3760ebe3.mjs → index.astro.5bc6b9d8.mjs} +34 -32
- package/packages/storefront/dist/server/entry.mjs +13 -12
- package/packages/storefront/dist/server/renderers.mjs +7 -8
- package/packages/storefront/package.json +2 -2
- package/packages/storefront/server.d.ts +3 -27
- package/packages/storefront/src/lib/$storefront.d.ts +28 -0
- package/packages/storefront/src/lib/assets/base.css +2 -0
- package/packages/storefront/src/lib/layouts/Base.astro +1 -9
- package/packages/storefront/src/lib/layouts/BaseHead.astro +73 -2
- package/packages/storefront/src/lib/layouts/use-page-main.ts +18 -5
- package/packages/storefront/src/lib/pages/_vue.ts +1 -1
- package/packages/storefront/src/lib/server-data.ts +1 -1
- package/packages/storefront/src/lib/ssr-context.ts +46 -36
- package/packages/storefront/src/vue-globals.d.ts +3 -3
- package/packages/types/package.json +1 -1
- package/pnpm-workspace.yaml +2 -2
- package/packages/storefront/dist/client/_astro/HeroSlider.eb156f18.js +0 -1
- package/packages/storefront/dist/client/_astro/_...slug_.c2da43fb.css +0 -1
- package/packages/storefront/dist/server/chunks/_...9e22b578.mjs +0 -35
- package/packages/storefront/src/lib/composables/use-hero-slider.ts +0 -26
- package/packages/storefront/src/lib/layouts/BaseStateJson.astro +0 -68
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Menu
|
|
3
|
+
as="div"
|
|
4
|
+
class="relative text-center text-sm text-base-800"
|
|
5
|
+
v-slot="{ open }"
|
|
6
|
+
>
|
|
7
|
+
<div v-if="open">
|
|
8
|
+
<span v-once>{{ initializeFirebaseAuth() }}</span>
|
|
9
|
+
</div>
|
|
10
|
+
<MenuButton class="outline-none">
|
|
11
|
+
<slot name="button" v-bind="{ open }" />
|
|
12
|
+
</MenuButton>
|
|
13
|
+
<Fade>
|
|
14
|
+
<MenuItems class="absolute -right-8 mt-2 w-56
|
|
15
|
+
rounded shadow ring-1 ring-black/5 bg-white
|
|
16
|
+
divide-y divide-base-100 focus:outline-none">
|
|
17
|
+
<div class="p-3 text-base-600">
|
|
18
|
+
{{ `${$t.i19hello} ${customerName || $t.i19visitor}` }}
|
|
19
|
+
<AccountLink role="button" class="ui-btn-sm ui-btn-primary block my-1">
|
|
20
|
+
{{ $t.i19accessMyAccount }}
|
|
21
|
+
</AccountLink>
|
|
22
|
+
<AccountLink v-if="!isLogged" is-sign-up class="ui-link block">
|
|
23
|
+
{{ $t.i19createAnAccount }}
|
|
24
|
+
</AccountLink>
|
|
25
|
+
</div>
|
|
26
|
+
<ul class="list-none">
|
|
27
|
+
<MenuItem as="li" v-slot="{ active }">
|
|
28
|
+
<AccountLink
|
|
29
|
+
to="orders"
|
|
30
|
+
class="block p-2"
|
|
31
|
+
:class="active ? 'bg-base-100 text-primary' : null"
|
|
32
|
+
>
|
|
33
|
+
{{ $t.i19myOrders }}
|
|
34
|
+
</AccountLink>
|
|
35
|
+
</MenuItem>
|
|
36
|
+
<MenuItem as="li" v-slot="{ active }">
|
|
37
|
+
<AccountLink
|
|
38
|
+
to="favorites"
|
|
39
|
+
class="block p-2"
|
|
40
|
+
:class="active ? 'bg-base-100 text-primary' : null"
|
|
41
|
+
>
|
|
42
|
+
<i class="i-heart mr-1"></i>
|
|
43
|
+
{{ $t.i19myFavorites }}
|
|
44
|
+
</AccountLink>
|
|
45
|
+
</MenuItem>
|
|
46
|
+
<MenuItem
|
|
47
|
+
as="li"
|
|
48
|
+
v-slot="{ active }"
|
|
49
|
+
v-for="({ title, href }, i) in serviceLinks"
|
|
50
|
+
:key="`s-${i}`"
|
|
51
|
+
>
|
|
52
|
+
<ALink
|
|
53
|
+
:href="href"
|
|
54
|
+
class="block p-2"
|
|
55
|
+
:class="active ? 'bg-base-100 text-primary' : null"
|
|
56
|
+
>
|
|
57
|
+
{{ title }}
|
|
58
|
+
</ALink>
|
|
59
|
+
</MenuItem>
|
|
60
|
+
<MenuItem as="li">
|
|
61
|
+
<div class="flex justify-center gap-2 p-2 text-base-500 text-base">
|
|
62
|
+
<span v-for="(href, network) in socialNetworks" :key="network">
|
|
63
|
+
<SocialNetworkLink :network="network" class="p-1 hover:text-primary" />
|
|
64
|
+
</span>
|
|
65
|
+
</div>
|
|
66
|
+
</MenuItem>
|
|
67
|
+
<MenuItem v-if="isLogged" as="li">
|
|
68
|
+
<button @click="logout" class="p-2 text-right text-base-800">
|
|
69
|
+
<span class="text-base-600">{{ $t.i19logout }}</span>
|
|
70
|
+
<i class="i-arrow-right-on-rectangle text-lg ml-1"></i>
|
|
71
|
+
</button>
|
|
72
|
+
</MenuItem>
|
|
73
|
+
</ul>
|
|
74
|
+
</MenuItems>
|
|
75
|
+
</Fade>
|
|
76
|
+
</Menu>
|
|
77
|
+
</template>
|
|
78
|
+
|
|
79
|
+
<script setup lang="ts">
|
|
80
|
+
import {
|
|
81
|
+
Menu,
|
|
82
|
+
MenuButton,
|
|
83
|
+
MenuItems,
|
|
84
|
+
MenuItem,
|
|
85
|
+
} from '@headlessui/vue';
|
|
86
|
+
import { socialNetworks } from '@@sf/server-data';
|
|
87
|
+
import {
|
|
88
|
+
customerName,
|
|
89
|
+
initializeFirebaseAuth,
|
|
90
|
+
isLogged,
|
|
91
|
+
logout,
|
|
92
|
+
} from '@@sf/state/customer-session';
|
|
93
|
+
import AccountLink from '@@sf/components/AccountLink.vue';
|
|
94
|
+
import SocialNetworkLink from '@@sf/components/SocialNetworkLink.vue';
|
|
95
|
+
|
|
96
|
+
export interface Props {
|
|
97
|
+
serviceLinks?: Array<{
|
|
98
|
+
title: string;
|
|
99
|
+
href: string;
|
|
100
|
+
}>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
defineProps<Props>();
|
|
104
|
+
</script>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="mx-auto overflow-x-hidden"
|
|
4
|
+
:class="hasHeader ? 'grid grid-cols-1 md:grid-cols-2 items-center' : null"
|
|
5
|
+
>
|
|
6
|
+
<component
|
|
7
|
+
:is="href ? 'a-link' : 'span'"
|
|
8
|
+
:href="href"
|
|
9
|
+
class="[&_img]:rounded-lg"
|
|
10
|
+
:class="hasHeader ? 'basis-1/2 grow-0 md:order-last' : '[&_img]:w-full'"
|
|
11
|
+
>
|
|
12
|
+
<slot name="picture" />
|
|
13
|
+
</component>
|
|
14
|
+
<div
|
|
15
|
+
v-if="hasHeader"
|
|
16
|
+
class="basis-1/2 grow-0 p-12 xl:ps-32 mb-3 md:mb-0"
|
|
17
|
+
>
|
|
18
|
+
<Component
|
|
19
|
+
v-if="parsedTitle"
|
|
20
|
+
:is="headingTag"
|
|
21
|
+
class="ui-title mt-1"
|
|
22
|
+
>
|
|
23
|
+
{{ parsedTitle }}
|
|
24
|
+
</Component>
|
|
25
|
+
<p v-if="parsedSubtitle" class="text-lg mt-4 md:mt-6">
|
|
26
|
+
{{ parsedSubtitle }}
|
|
27
|
+
</p>
|
|
28
|
+
<component
|
|
29
|
+
v-if="parsedButtonText"
|
|
30
|
+
:is="buttonLink ? 'a-link' : 'span'"
|
|
31
|
+
:href="buttonLink"
|
|
32
|
+
class="ui-btn-lg ui-btn-contrast min-w-[150px] mt-7 md:mt-10"
|
|
33
|
+
>
|
|
34
|
+
{{ parsedButtonText }}
|
|
35
|
+
</component>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import {
|
|
42
|
+
type Props as UseBannerProps,
|
|
43
|
+
useBanner,
|
|
44
|
+
} from '@@sf/composables/use-banner';
|
|
45
|
+
|
|
46
|
+
export type Props = UseBannerProps & {
|
|
47
|
+
headingTag?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
51
|
+
headingTag: 'h3',
|
|
52
|
+
});
|
|
53
|
+
const {
|
|
54
|
+
hasHeader,
|
|
55
|
+
parsedTitle,
|
|
56
|
+
parsedSubtitle,
|
|
57
|
+
parsedButtonText,
|
|
58
|
+
} = useBanner(props);
|
|
59
|
+
</script>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { Props as UseBannerProps } from '@@sf/composables/use-banner';
|
|
3
|
+
import BannerPictures from '@@sf/components/BannerPictures.astro';
|
|
4
|
+
import Banner from '~/components/Banner.vue';
|
|
5
|
+
|
|
6
|
+
export type Props = {
|
|
7
|
+
title?: string;
|
|
8
|
+
titleLink?: string;
|
|
9
|
+
banners: UseBannerProps[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { banners } = Astro.props;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<section class="ui-section">
|
|
16
|
+
<ul class="flex flex-wrap lg:flex-nowrap gap-4">
|
|
17
|
+
{banners.map((banner) => (
|
|
18
|
+
<li class="lg:flex-1 [&_img]:w-full [&_img]:object-cover">
|
|
19
|
+
<Banner {...banner}>
|
|
20
|
+
<BannerPictures {...banner} slot="picture" />
|
|
21
|
+
</Banner>
|
|
22
|
+
</li>
|
|
23
|
+
))}
|
|
24
|
+
</ul>
|
|
25
|
+
</section>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<aside class="flex flex-col h-full">
|
|
3
|
+
<header class="px-6 py-4 flex justify-end items-center gap-3">
|
|
4
|
+
<span class="text-right text-base text-base-600 font-medium">
|
|
5
|
+
{{ $t.i19myShoppingCart }}
|
|
6
|
+
</span>
|
|
7
|
+
<span class="ui-badge-pill-lg">
|
|
8
|
+
{{ totalItems }}
|
|
9
|
+
</span>
|
|
10
|
+
</header>
|
|
11
|
+
<article class="grow bg-base-50 border-t-2 border-b-2 border-base-100">
|
|
12
|
+
<div v-if="freeShippingFromValue" class="text-sm text-center">
|
|
13
|
+
<div
|
|
14
|
+
v-if="freeShippingFromValue > shoppingCart.subtotal"
|
|
15
|
+
class="p-3 secondary-subtle"
|
|
16
|
+
>
|
|
17
|
+
{{ $t.i19add$1ToEarn.replace('$1',
|
|
18
|
+
$money(freeShippingFromValue - shoppingCart.subtotal)) }}
|
|
19
|
+
<strong class="lowercase">{{ $t.i19freeShipping }}</strong>
|
|
20
|
+
</div>
|
|
21
|
+
<div v-else class="p-3 bg-success-50 text-success-800">
|
|
22
|
+
<i class="text-success-900 i-check mr-1"></i>
|
|
23
|
+
{{ $t.i19freeShippingFrom }} {{ $money(freeShippingFromValue) }}
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</article>
|
|
27
|
+
<footer class="p-5 shadow">
|
|
28
|
+
</footer>
|
|
29
|
+
</aside>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script setup lang="ts">
|
|
33
|
+
import { freeShippingFromValue } from '@@sf/state/modules-info';
|
|
34
|
+
import { shoppingCart, totalItems } from '@@sf/state/shopping-cart';
|
|
35
|
+
</script>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex w-[300px] justify-between">
|
|
3
|
+
<div>
|
|
4
|
+
<strong class="text-lg">{{ hours }}</strong>
|
|
5
|
+
<span>{{ $t.i19about }}</span>
|
|
6
|
+
</div>
|
|
7
|
+
<span>:</span>
|
|
8
|
+
<div>
|
|
9
|
+
{{ minutes }}
|
|
10
|
+
</div>
|
|
11
|
+
<span>:</span>
|
|
12
|
+
<div>
|
|
13
|
+
{{ seconds }}
|
|
14
|
+
</div>
|
|
15
|
+
<span>:</span>
|
|
16
|
+
<div>
|
|
17
|
+
{{ ms }}
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import {
|
|
24
|
+
ref,
|
|
25
|
+
computed,
|
|
26
|
+
watch,
|
|
27
|
+
watchEffect,
|
|
28
|
+
} from 'vue';
|
|
29
|
+
import { useIntervalFn } from '@vueuse/core';
|
|
30
|
+
|
|
31
|
+
export interface Props {
|
|
32
|
+
endsAt?: string;
|
|
33
|
+
maxHours?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
37
|
+
maxHours: 8,
|
|
38
|
+
});
|
|
39
|
+
const endTimestamp = computed(() => {
|
|
40
|
+
if (props.endsAt) {
|
|
41
|
+
return new Date(props.endsAt).getTime();
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
});
|
|
45
|
+
const hours = ref<number | null>(null);
|
|
46
|
+
const minutes = ref<number>(59);
|
|
47
|
+
const seconds = ref<number>(59);
|
|
48
|
+
const ms = ref<number>(999);
|
|
49
|
+
watchEffect(() => {
|
|
50
|
+
if (endTimestamp.value) {
|
|
51
|
+
const timeDiff = endTimestamp.value - Date.now();
|
|
52
|
+
if (timeDiff > 0) {
|
|
53
|
+
const diffHours = Math.floor(timeDiff / (1000 * 60 * 60));
|
|
54
|
+
hours.value = Math.min(diffHours, props.maxHours);
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
hours.value = props.maxHours;
|
|
59
|
+
});
|
|
60
|
+
useIntervalFn(() => {
|
|
61
|
+
ms.value -= 1;
|
|
62
|
+
if (ms.value < 0) {
|
|
63
|
+
ms.value = 999;
|
|
64
|
+
}
|
|
65
|
+
}, 1);
|
|
66
|
+
watch([ms], () => {
|
|
67
|
+
if (ms.value === 0) {
|
|
68
|
+
seconds.value -= 1;
|
|
69
|
+
if (seconds.value < 0) {
|
|
70
|
+
seconds.value = 59;
|
|
71
|
+
minutes.value -= 1;
|
|
72
|
+
if (minutes.value < 0) {
|
|
73
|
+
minutes.value = 59;
|
|
74
|
+
hours.value = Math.min((hours.value as number) - 1, 0);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
</script>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="bg-base-100 py-3">
|
|
3
|
+
<div class="relative container mx-auto">
|
|
4
|
+
<Carousel :autoplay="autoplay" class="[&>*]:items-center">
|
|
5
|
+
<li
|
|
6
|
+
v-for="(slide, i) in slides"
|
|
7
|
+
:key="i"
|
|
8
|
+
class="shrink-0 basis-full"
|
|
9
|
+
>
|
|
10
|
+
<Banner
|
|
11
|
+
v-bind="{ ...slide, headingTag: i === 0 ? 'h2' : 'h3' }"
|
|
12
|
+
class="w-full"
|
|
13
|
+
>
|
|
14
|
+
<template #picture>
|
|
15
|
+
<slot :name="`picture-${i}`" />
|
|
16
|
+
</template>
|
|
17
|
+
</Banner>
|
|
18
|
+
</li>
|
|
19
|
+
<template #controls>
|
|
20
|
+
<div
|
|
21
|
+
v-show="slides.length > 1"
|
|
22
|
+
class="absolute z-10 bottom-5 right-5 flex justify-end items-center"
|
|
23
|
+
>
|
|
24
|
+
<div class="relative w-20 h-10 rounded-full
|
|
25
|
+
bg-primary/80 text-on-primary shadow-sm ring-1 ring-black/5">
|
|
26
|
+
<CarouselControl class="w-10 h-10 rounded-full hover:bg-primary" is-prev>
|
|
27
|
+
<i class="i-arrow-left"></i>
|
|
28
|
+
</CarouselControl>
|
|
29
|
+
<CarouselControl class="w-10 h-10 rounded-full hover:bg-primary">
|
|
30
|
+
<i class="i-arrow-right"></i>
|
|
31
|
+
</CarouselControl>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
</Carousel>
|
|
36
|
+
</div>
|
|
37
|
+
</section>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import type { Props as UseBannerProps } from '@@sf/composables/use-banner';
|
|
42
|
+
import Carousel from '@@sf/components/Carousel.vue';
|
|
43
|
+
import CarouselControl from '@@sf/components/CarouselControl.vue';
|
|
44
|
+
import Banner from '~/components/Banner.vue';
|
|
45
|
+
|
|
46
|
+
export type Props = {
|
|
47
|
+
autoplay?: number;
|
|
48
|
+
slides: UseBannerProps[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
defineProps<Props>();
|
|
52
|
+
</script>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="bg-black relative z-20">
|
|
3
|
+
<div class="container md:w-2/3 mx-auto px-3 py-1 md:py-1.5">
|
|
4
|
+
<Carousel :autoplay="countValidSlides > 1 ? 7000 : undefined">
|
|
5
|
+
<li
|
|
6
|
+
v-for="(slide, i) in slides"
|
|
7
|
+
:key="i"
|
|
8
|
+
class="shrink-0 basis-full h-full text-center"
|
|
9
|
+
>
|
|
10
|
+
<component
|
|
11
|
+
:is="slide.href ? 'ALink' : 'span'"
|
|
12
|
+
:href="slide.href"
|
|
13
|
+
:target="slide.target"
|
|
14
|
+
class="inline-block px-8"
|
|
15
|
+
:class="slide.href ? 'hover:underline' : null"
|
|
16
|
+
>
|
|
17
|
+
<span
|
|
18
|
+
v-if="parsedContents[i]"
|
|
19
|
+
v-html="parsedContents[i]"
|
|
20
|
+
class="prose text-sm md:text-base lg:tracking-wide
|
|
21
|
+
text-base-200 uppercase font-semibold
|
|
22
|
+
[&_b]:text-base-100 [&_strong]:font-black
|
|
23
|
+
[&_strong]:text-transparent [&_strong]:bg-clip-text [&_strong]:bg-gradient-to-r
|
|
24
|
+
[&_strong]:from-yellow-200 [&_strong]:to-yellow-400"
|
|
25
|
+
></span>
|
|
26
|
+
</component>
|
|
27
|
+
</li>
|
|
28
|
+
<template #controls>
|
|
29
|
+
<div
|
|
30
|
+
v-show="countValidSlides > 1"
|
|
31
|
+
class="text-xl leading-none text-base-300"
|
|
32
|
+
>
|
|
33
|
+
<CarouselControl class="pr-2 bg-black hover:text-white" is-prev />
|
|
34
|
+
<CarouselControl class="pl-2 bg-black hover:text-white" />
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
37
|
+
</Carousel>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<script setup lang="ts">
|
|
43
|
+
import {
|
|
44
|
+
type Props as UsePitchBarProps,
|
|
45
|
+
usePitchBar,
|
|
46
|
+
} from '@@sf/composables/use-pitch-bar';
|
|
47
|
+
import Carousel from '@@sf/components/Carousel.vue';
|
|
48
|
+
import CarouselControl from '@@sf/components/CarouselControl.vue';
|
|
49
|
+
|
|
50
|
+
export interface Props extends UsePitchBarProps {}
|
|
51
|
+
|
|
52
|
+
const props = defineProps<Props>();
|
|
53
|
+
const {
|
|
54
|
+
parsedContents,
|
|
55
|
+
countValidSlides,
|
|
56
|
+
} = usePitchBar(props);
|
|
57
|
+
</script>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="text-base-600
|
|
4
|
+
[&>div]:[font-size:90%] [&_small]:lowercase [&_small]:[font-size:92%]"
|
|
5
|
+
:class="isBig ? 'text-lg' : null"
|
|
6
|
+
data-prices
|
|
7
|
+
>
|
|
8
|
+
<span v-if="comparePrice" class="text-base-500 mr-1 [font-size:87%]">
|
|
9
|
+
<small v-if="isLiteral">
|
|
10
|
+
{{ `${$t.i19from} ` }}
|
|
11
|
+
</small>
|
|
12
|
+
<s>{{ $money(comparePrice) }}</s>
|
|
13
|
+
<small v-if="isLiteral">
|
|
14
|
+
{{ ` ${$t.i19to}` }}
|
|
15
|
+
</small>
|
|
16
|
+
</span>
|
|
17
|
+
<strong
|
|
18
|
+
class="inline-block text-base-800"
|
|
19
|
+
:class="isBig ? 'text-5xl block' : null"
|
|
20
|
+
>
|
|
21
|
+
<small v-if="hasVariedPrices">
|
|
22
|
+
{{ `${$t.i19asOf} ` }}
|
|
23
|
+
</small>
|
|
24
|
+
{{ $money(salePrice) }}
|
|
25
|
+
</strong>
|
|
26
|
+
<Fade slide="down">
|
|
27
|
+
<div v-if="cashbackValue && hasCashback" class="relative z-10">
|
|
28
|
+
<span :data-tooltip="$t.i19get$1back
|
|
29
|
+
.replace('$1', $percentage(cashbackPercentage))">
|
|
30
|
+
<i class="i-arrow-uturn-left mr-1"></i>
|
|
31
|
+
<span class="font-medium">
|
|
32
|
+
{{ $money(cashbackValue) }}
|
|
33
|
+
</span>
|
|
34
|
+
<small> cashback</small>
|
|
35
|
+
</span>
|
|
36
|
+
</div>
|
|
37
|
+
</Fade>
|
|
38
|
+
<Fade slide="down">
|
|
39
|
+
<div v-if="installmentValue && hasPriceOptions">
|
|
40
|
+
<small v-if="isLiteral">
|
|
41
|
+
{{ `${$t.i19upTo} ` }}
|
|
42
|
+
</small>
|
|
43
|
+
{{ installmentsNumber }}x
|
|
44
|
+
<small v-if="isLiteral">
|
|
45
|
+
{{ ` ${$t.i19of} ` }}
|
|
46
|
+
</small>
|
|
47
|
+
<span>{{ $money(installmentValue) }}</span>
|
|
48
|
+
<small v-if="!monthlyInterest && isLiteral">
|
|
49
|
+
{{ $t.i19interestFree }}
|
|
50
|
+
</small>
|
|
51
|
+
</div>
|
|
52
|
+
</Fade>
|
|
53
|
+
<Fade slide="down">
|
|
54
|
+
<div v-if="priceWithDiscount < salePrice && hasPriceOptions">
|
|
55
|
+
<small v-if="!discountLabel">
|
|
56
|
+
{{ `${$t.i19asOf} ` }}
|
|
57
|
+
</small>
|
|
58
|
+
<span>{{ $money(priceWithDiscount) }}</span>
|
|
59
|
+
<small v-if="discountLabel">
|
|
60
|
+
{{ ` ${discountLabel}` }}
|
|
61
|
+
</small>
|
|
62
|
+
</div>
|
|
63
|
+
</Fade>
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<script setup lang="ts">
|
|
68
|
+
import {
|
|
69
|
+
type Props as UsePricesProps,
|
|
70
|
+
usePrices,
|
|
71
|
+
} from '@@sf/composables/use-prices';
|
|
72
|
+
|
|
73
|
+
export interface Props extends UsePricesProps {
|
|
74
|
+
isBig?: boolean;
|
|
75
|
+
isLiteral?: boolean;
|
|
76
|
+
hasCashback?: boolean;
|
|
77
|
+
hasPriceOptions?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
81
|
+
hasCashback: true,
|
|
82
|
+
hasPriceOptions: true,
|
|
83
|
+
});
|
|
84
|
+
const {
|
|
85
|
+
hasVariedPrices,
|
|
86
|
+
salePrice,
|
|
87
|
+
comparePrice,
|
|
88
|
+
cashbackPercentage,
|
|
89
|
+
cashbackValue,
|
|
90
|
+
installmentsNumber,
|
|
91
|
+
monthlyInterest,
|
|
92
|
+
installmentValue,
|
|
93
|
+
priceWithDiscount,
|
|
94
|
+
discountLabel,
|
|
95
|
+
} = usePrices(props);
|
|
96
|
+
</script>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<article
|
|
3
|
+
ref="card"
|
|
4
|
+
:data-sku="product.sku"
|
|
5
|
+
class="relative h-full max-w-[350px] mx-auto py-3 group"
|
|
6
|
+
>
|
|
7
|
+
<component
|
|
8
|
+
:is="link ? 'ALink' : 'span'"
|
|
9
|
+
:href="link"
|
|
10
|
+
class="flex flex-col h-full rounded overflow-hidden
|
|
11
|
+
group-hover:shadow group-hover:ring-1 ring-black/5"
|
|
12
|
+
>
|
|
13
|
+
<div class="aspect-square p-2
|
|
14
|
+
motion-safe:group-hover:scale-110 transition-transform">
|
|
15
|
+
<div class="relative w-full h-full bg-white rounded overflow-hidden
|
|
16
|
+
group-hover:rounded-none">
|
|
17
|
+
<template v-if="images?.length">
|
|
18
|
+
<AImg
|
|
19
|
+
:picture="images[0]"
|
|
20
|
+
:alt="title"
|
|
21
|
+
class="absolute top-0 left-0 block w-full h-full object-cover"
|
|
22
|
+
/>
|
|
23
|
+
<AImg
|
|
24
|
+
v-if="images[1] && wasHoveredOnce"
|
|
25
|
+
:picture="images[1]"
|
|
26
|
+
:alt="title"
|
|
27
|
+
class="absolute top-0 left-0 block w-full h-full object-cover
|
|
28
|
+
opacity-0 group-hover:opacity-100 transition-opacity
|
|
29
|
+
motion-safe:duration-300 text-transparent z-10"
|
|
30
|
+
/>
|
|
31
|
+
</template>
|
|
32
|
+
<div
|
|
33
|
+
v-else
|
|
34
|
+
class="w-full h-full bg-gradient-to-br from-base-50/20 to-base-100"
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
<span
|
|
39
|
+
v-if="discountPercentage"
|
|
40
|
+
class=":uno: absolute top-9 right-2
|
|
41
|
+
group-hover:scale-110 group-hover:translate-x-2 transition-transform
|
|
42
|
+
bg-secondary/70 text-on-secondary text-xs
|
|
43
|
+
py-0.5 pr-1.5 pl-3 [clip-path:polygon(20%_0,100%_0,100%_100%,0_100%)]"
|
|
44
|
+
>
|
|
45
|
+
-<strong>{{ discountPercentage }}</strong>%
|
|
46
|
+
</span>
|
|
47
|
+
<div class="relative flex flex-col grow justify-between p-4
|
|
48
|
+
group-hover:backdrop-blur-md bg-white/40 z-10">
|
|
49
|
+
<component
|
|
50
|
+
:is="headingTag"
|
|
51
|
+
class="ui-link uppercase font-bold text-sm no-underline line-clamp-2"
|
|
52
|
+
:class="[
|
|
53
|
+
isActive ? 'text-base-700' : 'text-base-500',
|
|
54
|
+
link ? 'group-hover:underline group-hover:text-primary' : null,
|
|
55
|
+
]"
|
|
56
|
+
>
|
|
57
|
+
{{ title }}
|
|
58
|
+
</component>
|
|
59
|
+
<div class="pt-2">
|
|
60
|
+
<div v-if="isActive">
|
|
61
|
+
<Prices :product="product" />
|
|
62
|
+
</div>
|
|
63
|
+
<span v-else class="ui-badge bg-warning-100 text-warning-700">
|
|
64
|
+
{{ !isInStock ? $t.i19outOfStock : $t.i19inactive }}
|
|
65
|
+
</span>
|
|
66
|
+
</div>
|
|
67
|
+
<button
|
|
68
|
+
v-if="isActive && !hasVariations"
|
|
69
|
+
class=":uno: hidden md:block ui-btn-sm ui-btn-primary
|
|
70
|
+
absolute -top-6 left-0 w-full rounded-none
|
|
71
|
+
opacity-0 group-hover:opacity-100 transition -z-10 group-hover:z-10"
|
|
72
|
+
@click.prevent="addProductToCart(product)"
|
|
73
|
+
>
|
|
74
|
+
<i class="i-plus-20-solid mr-0.5"></i>
|
|
75
|
+
{{ $t.i19addToCart }}
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
</component>
|
|
79
|
+
</article>
|
|
80
|
+
</template>
|
|
81
|
+
|
|
82
|
+
<script setup lang="ts">
|
|
83
|
+
import { ref } from 'vue';
|
|
84
|
+
import { watchOnce, useElementHover } from '@vueuse/core';
|
|
85
|
+
import { addProductToCart } from '@@sf/state/shopping-cart';
|
|
86
|
+
import {
|
|
87
|
+
type Props as UseProductCardProps,
|
|
88
|
+
useProductCard,
|
|
89
|
+
} from '@@sf/composables/use-product-card';
|
|
90
|
+
import Prices from '~/components/Prices.vue';
|
|
91
|
+
|
|
92
|
+
export type Props = UseProductCardProps & {
|
|
93
|
+
headingTag?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
97
|
+
headingTag: 'h3',
|
|
98
|
+
});
|
|
99
|
+
const {
|
|
100
|
+
product,
|
|
101
|
+
title,
|
|
102
|
+
link,
|
|
103
|
+
images,
|
|
104
|
+
isInStock,
|
|
105
|
+
isActive,
|
|
106
|
+
discountPercentage,
|
|
107
|
+
hasVariations,
|
|
108
|
+
} = useProductCard({
|
|
109
|
+
product: props.product,
|
|
110
|
+
productId: props.productId,
|
|
111
|
+
} as UseProductCardProps);
|
|
112
|
+
const card = ref<HTMLElement | null>(null);
|
|
113
|
+
const isHovered = useElementHover(card);
|
|
114
|
+
const wasHoveredOnce = ref(false);
|
|
115
|
+
watchOnce(isHovered, () => {
|
|
116
|
+
wasHoveredOnce.value = true;
|
|
117
|
+
});
|
|
118
|
+
</script>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="ui-section">
|
|
3
|
+
<div v-if="title" class="max-w-prose mx-auto text-center mb-2">
|
|
4
|
+
<h2 class="ui-text-brand text-3xl">
|
|
5
|
+
<ALink v-if="titleLink" :href="titleLink" class="ui-link">
|
|
6
|
+
{{ title }}
|
|
7
|
+
</ALink>
|
|
8
|
+
<span v-else class="text-base-700">
|
|
9
|
+
{{ title }}
|
|
10
|
+
</span>
|
|
11
|
+
</h2>
|
|
12
|
+
</div>
|
|
13
|
+
<Carousel class="group/shelf">
|
|
14
|
+
<li
|
|
15
|
+
v-for="product in products"
|
|
16
|
+
:key="product._id"
|
|
17
|
+
class="basis-1/2 md:basis-1/3 lg:basis-1/4 shrink-0"
|
|
18
|
+
>
|
|
19
|
+
<ProductCard :product="product" />
|
|
20
|
+
</li>
|
|
21
|
+
<template #controls>
|
|
22
|
+
<div
|
|
23
|
+
v-show="products.length > 2"
|
|
24
|
+
class="text-3xl lg:text-2xl leading-none text-primary
|
|
25
|
+
lg:opacity-0 group-hover/shelf:opacity-90 transition-opacity"
|
|
26
|
+
>
|
|
27
|
+
<CarouselControl class="!top-1/2 !-left-4 w-12 h-12
|
|
28
|
+
bg-transparent lg:bg-white/80 lg:hover:bg-primary-300/60 rounded-full
|
|
29
|
+
lg:shadow-sm lg:ring-1 ring-black/5" is-prev />
|
|
30
|
+
<CarouselControl class="!top-1/2 !-right-4 w-12 h-12
|
|
31
|
+
bg-transparent lg:bg-white/80 lg:hover:bg-primary-300/60 rounded-full
|
|
32
|
+
lg:shadow-sm lg:ring-1 ring-black/5" />
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
</Carousel>
|
|
36
|
+
</section>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script setup lang="ts">
|
|
40
|
+
import {
|
|
41
|
+
type Props as UseProductShelfProps,
|
|
42
|
+
useProductShelf,
|
|
43
|
+
} from '@@sf/composables/use-product-shelf';
|
|
44
|
+
import Carousel from '@@sf/components/Carousel.vue';
|
|
45
|
+
import CarouselControl from '@@sf/components/CarouselControl.vue';
|
|
46
|
+
import ProductCard from '~/components/ProductCard.vue';
|
|
47
|
+
|
|
48
|
+
export interface Props extends UseProductShelfProps {}
|
|
49
|
+
|
|
50
|
+
const props = defineProps<Props>();
|
|
51
|
+
const {
|
|
52
|
+
title,
|
|
53
|
+
titleLink,
|
|
54
|
+
fetching,
|
|
55
|
+
products,
|
|
56
|
+
} = useProductShelf(props);
|
|
57
|
+
if (import.meta.env.SSR) {
|
|
58
|
+
await fetching;
|
|
59
|
+
}
|
|
60
|
+
</script>
|