create-nymbal-app 0.3.0 → 0.5.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/index.d.ts +2 -1
- package/dist/index.js +14 -12
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/scaffold/project/astro/src/pages/index.astro +13 -15
- package/scaffold/templates/astro/_dot.node-version +1 -1
- package/scaffold/templates/astro/astro.config.mjs +23 -0
- package/scaffold/templates/astro/package.json +26 -27
- package/scaffold/templates/astro/src/components/SkipNav.astro +3 -1
- package/scaffold/templates/astro/src/elements/index.ts +11 -0
- package/scaffold/templates/astro/src/layouts/BaseLayout.astro +15 -4
- package/scaffold/templates/astro/src/lib/api.ts +126 -20
- package/scaffold/templates/astro/src/lib/chrome.ts +59 -0
- package/scaffold/templates/astro/src/lib/client.ts +4 -2
- package/scaffold/templates/astro/src/lib/fragments.ts +48 -0
- package/scaffold/templates/astro/src/lib/server-elements.ts +5 -0
- package/scaffold/templates/astro/src/middleware.ts +37 -0
- package/scaffold/templates/astro/src/pages/account/addresses.astro +5 -97
- package/scaffold/templates/astro/src/pages/account/login.astro +7 -34
- package/scaffold/templates/astro/src/pages/account/profile.astro +6 -56
- package/scaffold/templates/astro/src/pages/account/register.astro +7 -73
- package/scaffold/templates/astro/src/pages/cart.astro +14 -7
- package/scaffold/templates/astro/src/pages/category/[slug].astro +41 -26
- package/scaffold/templates/astro/src/pages/checkout.astro +34 -90
- package/scaffold/templates/astro/src/pages/fragments/footer.astro +5 -2
- package/scaffold/templates/astro/src/pages/fragments/header.astro +12 -2
- package/scaffold/templates/astro/src/pages/fragments/nav.astro +8 -5
- package/scaffold/templates/astro/src/pages/fragments/product/[slug].astro +9 -10
- package/scaffold/templates/astro/src/pages/index.astro +18 -15
- package/scaffold/templates/astro/src/pages/order/[orderNumber].astro +22 -44
- package/scaffold/templates/astro/src/pages/product/[slug].astro +46 -29
- package/scaffold/templates/astro/src/pages/products/index.astro +40 -26
- package/scaffold/templates/astro/src/pages/search.astro +13 -8
- package/scaffold/templates/astro/src/scripts/init-client.ts +14 -0
- package/scaffold/templates/astro/src/scripts/pdp.ts +47 -53
- package/scaffold/templates/astro/src/scripts/plp.ts +21 -98
- package/scaffold/templates/astro/src/scripts/search.ts +19 -120
- package/scaffold/templates/astro/src/scripts/signed-in-redirect.ts +4 -0
- package/scaffold/templates/astro/src/styles/global.css +49 -45
- package/scaffold/templates/astro/src/styles/pages/account.css +0 -90
- package/scaffold/templates/astro/src/styles/pages/confirmation.css +6 -0
- package/scaffold/templates/astro/src/styles/pages/home.css +9 -100
- package/scaffold/templates/astro/src/styles/pages/pdp.css +5 -167
- package/scaffold/templates/astro/src/styles/pages/plp.css +3 -42
- package/scaffold/templates/astro/src/styles/pages/search.css +13 -100
- package/scaffold/templates/astro/src/styles/tokens.css +56 -0
- package/scaffold/templates/nextjs/_dot.node-version +1 -1
- package/scaffold/templates/nextjs/app/(catalog)/category/[slug]/page.tsx +74 -0
- package/scaffold/templates/nextjs/app/(catalog)/home/[key]/page.tsx +80 -0
- package/scaffold/templates/nextjs/app/(catalog)/layout.tsx +13 -0
- package/scaffold/templates/nextjs/app/(catalog)/not-found.tsx +7 -0
- package/scaffold/templates/nextjs/app/(catalog)/product/[slug]/page.tsx +182 -0
- package/scaffold/templates/nextjs/app/(catalog)/products/page.tsx +62 -0
- package/scaffold/templates/nextjs/app/(catalog)/search/SearchView.tsx +90 -0
- package/scaffold/templates/nextjs/app/(catalog)/search/page.tsx +46 -0
- package/scaffold/templates/nextjs/app/(shopper)/account/addresses/AddressesView.tsx +5 -0
- package/scaffold/templates/nextjs/app/(shopper)/account/addresses/page.tsx +17 -0
- package/scaffold/templates/nextjs/app/(shopper)/account/login/LoginView.tsx +28 -0
- package/scaffold/templates/nextjs/app/(shopper)/account/login/page.tsx +23 -0
- package/scaffold/templates/nextjs/app/(shopper)/account/profile/ProfileView.tsx +5 -0
- package/scaffold/templates/nextjs/app/(shopper)/account/profile/page.tsx +20 -0
- package/scaffold/templates/nextjs/app/(shopper)/account/register/RegisterView.tsx +26 -0
- package/scaffold/templates/nextjs/app/(shopper)/account/register/page.tsx +11 -0
- package/scaffold/templates/nextjs/app/(shopper)/cart/CartView.tsx +5 -0
- package/scaffold/templates/nextjs/app/(shopper)/cart/page.tsx +22 -0
- package/scaffold/templates/nextjs/app/(shopper)/checkout/CheckoutView.tsx +27 -0
- package/scaffold/templates/nextjs/app/(shopper)/checkout/page.tsx +39 -0
- package/scaffold/templates/nextjs/app/(shopper)/layout.tsx +12 -0
- package/scaffold/templates/nextjs/app/{order → (shopper)/order}/[orderNumber]/page.tsx +24 -5
- package/scaffold/templates/nextjs/app/error.tsx +3 -7
- package/scaffold/templates/nextjs/app/fragments/footer/route.ts +13 -0
- package/scaffold/templates/nextjs/app/fragments/header/[key]/route.ts +18 -0
- package/scaffold/templates/nextjs/app/fragments/nav/[key]/route.ts +23 -0
- package/scaffold/templates/nextjs/app/fragments/product/[slug]/route.ts +22 -0
- package/scaffold/templates/nextjs/app/globals.css +5 -23
- package/scaffold/templates/nextjs/app/layout.tsx +8 -5
- package/scaffold/templates/nextjs/app/not-found.tsx +9 -33
- package/scaffold/templates/nextjs/app/tokens.css +56 -0
- package/scaffold/templates/nextjs/components/client/ListingControls.tsx +87 -0
- package/scaffold/templates/nextjs/components/client/NymbalSetup.tsx +48 -6
- package/scaffold/templates/nextjs/components/client/ProductInteractive.tsx +64 -28
- package/scaffold/templates/nextjs/components/client/elements.tsx +15 -0
- package/scaffold/templates/nextjs/components/server/Header.tsx +33 -97
- package/scaffold/templates/nextjs/components/server/NotFoundView.tsx +39 -0
- package/scaffold/templates/nextjs/components/server/SkipNav.tsx +7 -3
- package/scaffold/templates/nextjs/elements/index.ts +12 -0
- package/scaffold/templates/nextjs/lib/api.ts +162 -46
- package/scaffold/templates/nextjs/lib/chrome.ts +40 -0
- package/scaffold/templates/nextjs/lib/client.ts +3 -0
- package/scaffold/templates/nextjs/lib/fragments.ts +16 -0
- package/scaffold/templates/nextjs/lib/header.ts +39 -0
- package/scaffold/templates/nextjs/lib/render.ts +14 -0
- package/scaffold/templates/nextjs/next.config.mjs +32 -0
- package/scaffold/templates/nextjs/package.json +28 -29
- package/scaffold/templates/nextjs/styles/pages/account.module.css +0 -70
- package/scaffold/templates/nextjs/styles/pages/confirmation.module.css +6 -85
- package/scaffold/templates/nextjs/styles/pages/home.module.css +2 -103
- package/scaffold/templates/nextjs/styles/pages/pdp.module.css +3 -179
- package/scaffold/templates/nextjs/styles/pages/plp.module.css +8 -141
- package/scaffold/templates/nextjs/styles/pages/search.module.css +8 -128
- package/scaffold/templates/nextjs/tsconfig.dist.json +5 -0
- package/scaffold/templates/astro/src/components/AddressForm.astro +0 -107
- package/scaffold/templates/astro/src/components/Breadcrumb.astro +0 -20
- package/scaffold/templates/astro/src/components/CategoryNav.astro +0 -27
- package/scaffold/templates/astro/src/components/CheckoutSteps.astro +0 -29
- package/scaffold/templates/astro/src/components/Footer.astro +0 -108
- package/scaffold/templates/astro/src/components/Header.astro +0 -192
- package/scaffold/templates/astro/src/components/ImageGallery.astro +0 -42
- package/scaffold/templates/astro/src/components/OrderSummary.astro +0 -53
- package/scaffold/templates/astro/src/components/Pagination.astro +0 -13
- package/scaffold/templates/astro/src/components/PriceDisplay.astro +0 -25
- package/scaffold/templates/astro/src/components/ProductCard.astro +0 -44
- package/scaffold/templates/astro/src/components/RelatedProducts.astro +0 -28
- package/scaffold/templates/astro/src/components/ReviewForm.astro +0 -74
- package/scaffold/templates/astro/src/components/ReviewList.astro +0 -41
- package/scaffold/templates/astro/src/components/SortDropdown.astro +0 -26
- package/scaffold/templates/astro/src/components/StockBadge.astro +0 -32
- package/scaffold/templates/astro/src/scripts/auth-forms.ts +0 -82
- package/scaffold/templates/astro/src/scripts/cart-page.ts +0 -143
- package/scaffold/templates/astro/src/scripts/checkout.ts +0 -272
- package/scaffold/templates/astro/src/scripts/image-gallery.ts +0 -22
- package/scaffold/templates/astro/src/scripts/profile-form.ts +0 -57
- package/scaffold/templates/astro/src/scripts/review-form.ts +0 -27
- package/scaffold/templates/astro/src/styles/pages/cart.css +0 -243
- package/scaffold/templates/astro/src/styles/pages/checkout.css +0 -270
- package/scaffold/templates/nextjs/app/account/addresses/page.tsx +0 -10
- package/scaffold/templates/nextjs/app/account/login/page.tsx +0 -23
- package/scaffold/templates/nextjs/app/account/profile/page.tsx +0 -10
- package/scaffold/templates/nextjs/app/account/register/page.tsx +0 -10
- package/scaffold/templates/nextjs/app/cart/page.tsx +0 -10
- package/scaffold/templates/nextjs/app/category/[slug]/page.tsx +0 -58
- package/scaffold/templates/nextjs/app/checkout/page.tsx +0 -10
- package/scaffold/templates/nextjs/app/page.tsx +0 -63
- package/scaffold/templates/nextjs/app/product/[slug]/page.tsx +0 -111
- package/scaffold/templates/nextjs/app/products/page.tsx +0 -40
- package/scaffold/templates/nextjs/app/search/page.tsx +0 -24
- package/scaffold/templates/nextjs/components/client/AddressManager.tsx +0 -181
- package/scaffold/templates/nextjs/components/client/CartPage.tsx +0 -120
- package/scaffold/templates/nextjs/components/client/CheckoutForm.tsx +0 -523
- package/scaffold/templates/nextjs/components/client/ImageGallery.tsx +0 -68
- package/scaffold/templates/nextjs/components/client/LoginForm.tsx +0 -89
- package/scaffold/templates/nextjs/components/client/MiniCartWrapper.tsx +0 -12
- package/scaffold/templates/nextjs/components/client/ProductGrid.tsx +0 -148
- package/scaffold/templates/nextjs/components/client/ProfileForm.tsx +0 -112
- package/scaffold/templates/nextjs/components/client/RegisterForm.tsx +0 -131
- package/scaffold/templates/nextjs/components/client/ReviewForm.tsx +0 -116
- package/scaffold/templates/nextjs/components/client/SearchBarWrapper.tsx +0 -27
- package/scaffold/templates/nextjs/components/client/SearchResults.tsx +0 -141
- package/scaffold/templates/nextjs/components/client/SortDropdown.tsx +0 -35
- package/scaffold/templates/nextjs/components/server/Breadcrumb.tsx +0 -36
- package/scaffold/templates/nextjs/components/server/CategoryNav.tsx +0 -29
- package/scaffold/templates/nextjs/components/server/CheckoutSteps.tsx +0 -60
- package/scaffold/templates/nextjs/components/server/Footer.module.css +0 -64
- package/scaffold/templates/nextjs/components/server/Footer.tsx +0 -79
- package/scaffold/templates/nextjs/components/server/Header.module.css +0 -147
- package/scaffold/templates/nextjs/components/server/OrderSummary.tsx +0 -79
- package/scaffold/templates/nextjs/components/server/PriceDisplay.tsx +0 -31
- package/scaffold/templates/nextjs/components/server/ProductCard.tsx +0 -59
- package/scaffold/templates/nextjs/components/server/RelatedProducts.tsx +0 -31
- package/scaffold/templates/nextjs/components/server/ReviewList.tsx +0 -62
- package/scaffold/templates/nextjs/components/server/StockBadge.tsx +0 -19
- package/scaffold/templates/nextjs/styles/pages/cart.module.css +0 -240
- package/scaffold/templates/nextjs/styles/pages/checkout.module.css +0 -404
- /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/layout.tsx +0 -0
- /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/orders/[orderNumber]/page.tsx +0 -0
- /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/orders/page.tsx +0 -0
- /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/page.tsx +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import '../../styles/pages/account.css'
|
|
3
|
+
import NymbalElement from '@nymbalhq/web-components/astro'
|
|
3
4
|
import BaseLayout from '../../layouts/BaseLayout.astro'
|
|
4
5
|
---
|
|
5
6
|
|
|
@@ -9,78 +10,7 @@ import BaseLayout from '../../layouts/BaseLayout.astro'
|
|
|
9
10
|
<h1 class="auth-card-title">Create Account</h1>
|
|
10
11
|
<p class="auth-card-subtitle">Join us and start shopping.</p>
|
|
11
12
|
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
<form id="register-form" data-testid="register-form">
|
|
15
|
-
<div class="account-form-row">
|
|
16
|
-
<div class="form-group">
|
|
17
|
-
<label for="register-firstName">First Name</label>
|
|
18
|
-
<input
|
|
19
|
-
type="text"
|
|
20
|
-
id="register-firstName"
|
|
21
|
-
name="firstName"
|
|
22
|
-
autocomplete="given-name"
|
|
23
|
-
placeholder="First name"
|
|
24
|
-
data-testid="register-firstName"
|
|
25
|
-
/>
|
|
26
|
-
</div>
|
|
27
|
-
|
|
28
|
-
<div class="form-group">
|
|
29
|
-
<label for="register-lastName">Last Name</label>
|
|
30
|
-
<input
|
|
31
|
-
type="text"
|
|
32
|
-
id="register-lastName"
|
|
33
|
-
name="lastName"
|
|
34
|
-
autocomplete="family-name"
|
|
35
|
-
placeholder="Last name"
|
|
36
|
-
data-testid="register-lastName"
|
|
37
|
-
/>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<div class="form-group">
|
|
42
|
-
<label for="register-email">Email</label>
|
|
43
|
-
<input
|
|
44
|
-
type="email"
|
|
45
|
-
id="register-email"
|
|
46
|
-
name="email"
|
|
47
|
-
required
|
|
48
|
-
autocomplete="email"
|
|
49
|
-
placeholder="you@example.com"
|
|
50
|
-
data-testid="register-email"
|
|
51
|
-
/>
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
<div class="form-group">
|
|
55
|
-
<label for="register-password">Password</label>
|
|
56
|
-
<input
|
|
57
|
-
type="password"
|
|
58
|
-
id="register-password"
|
|
59
|
-
name="password"
|
|
60
|
-
required
|
|
61
|
-
autocomplete="new-password"
|
|
62
|
-
placeholder="Choose a password"
|
|
63
|
-
minlength="8"
|
|
64
|
-
data-testid="register-password"
|
|
65
|
-
/>
|
|
66
|
-
</div>
|
|
67
|
-
|
|
68
|
-
<div class="form-group">
|
|
69
|
-
<label for="register-phone">Phone (optional)</label>
|
|
70
|
-
<input
|
|
71
|
-
type="tel"
|
|
72
|
-
id="register-phone"
|
|
73
|
-
name="phone"
|
|
74
|
-
autocomplete="tel"
|
|
75
|
-
placeholder="+44 7700 900000"
|
|
76
|
-
data-testid="register-phone"
|
|
77
|
-
/>
|
|
78
|
-
</div>
|
|
79
|
-
|
|
80
|
-
<button type="submit" class="btn btn-primary" data-testid="register-submit">
|
|
81
|
-
Create Account
|
|
82
|
-
</button>
|
|
83
|
-
</form>
|
|
13
|
+
<NymbalElement tag="nymbal-register-form" />
|
|
84
14
|
|
|
85
15
|
<p class="auth-footer">
|
|
86
16
|
Already have an account? <a href="/account/login">Sign in</a>
|
|
@@ -90,5 +20,9 @@ import BaseLayout from '../../layouts/BaseLayout.astro'
|
|
|
90
20
|
</BaseLayout>
|
|
91
21
|
|
|
92
22
|
<script>
|
|
93
|
-
import '../../
|
|
23
|
+
import '../../lib/client.js'
|
|
24
|
+
import { NymbalRegisterForm } from '@nymbalhq/web-components/elements/nymbal-register-form'
|
|
25
|
+
import '../../scripts/signed-in-redirect.js'
|
|
26
|
+
|
|
27
|
+
NymbalRegisterForm.define()
|
|
94
28
|
</script>
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
---
|
|
2
|
-
import '
|
|
2
|
+
import NymbalElement from '@nymbalhq/web-components/astro'
|
|
3
3
|
import BaseLayout from '../layouts/BaseLayout.astro'
|
|
4
|
+
import { fetchCart } from '../lib/api'
|
|
5
|
+
|
|
6
|
+
const cart = await fetchCart(Astro.request.headers.get('cookie'))
|
|
4
7
|
---
|
|
5
8
|
|
|
6
|
-
<BaseLayout title="Your Cart | Nymbal" description="Review the items in your cart.">
|
|
7
|
-
<div class="container section"
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
<BaseLayout title="Your Cart | Nymbal" description="Review the items in your cart." cart={cart}>
|
|
10
|
+
<div class="container section" data-testid="cart-page">
|
|
11
|
+
<NymbalElement
|
|
12
|
+
tag="nymbal-cart-page"
|
|
13
|
+
attributes={cart === null ? {} : { cart: JSON.stringify(cart) }}
|
|
14
|
+
/>
|
|
11
15
|
</div>
|
|
12
16
|
</BaseLayout>
|
|
13
17
|
|
|
14
18
|
<script>
|
|
15
|
-
import '../
|
|
19
|
+
import '../lib/client.js'
|
|
20
|
+
import { NymbalCartPage } from '@nymbalhq/web-components/elements/nymbal-cart-page'
|
|
21
|
+
|
|
22
|
+
NymbalCartPage.define()
|
|
16
23
|
</script>
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
---
|
|
2
2
|
import '../../styles/pages/plp.css'
|
|
3
|
-
import '../../styles/pages/home.css'
|
|
4
3
|
import BaseLayout from '../../layouts/BaseLayout.astro'
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import { fetchProducts, fetchCategories, sortFromUrl } from '../../lib/api'
|
|
4
|
+
import NymbalElement from '@nymbalhq/web-components/astro'
|
|
5
|
+
import { cardImageOf, cardProductOf } from '@nymbalhq/web-components/ssr'
|
|
6
|
+
import { PRODUCT_SORT_LABELS } from '@nymbalhq/types'
|
|
7
|
+
import { fetchProducts, fetchCategories, fetchCapabilities, sortFromUrl } from '../../lib/api'
|
|
10
8
|
|
|
11
9
|
const { slug } = Astro.params
|
|
12
|
-
const
|
|
10
|
+
const capabilities = await fetchCapabilities()
|
|
11
|
+
const sortKeys = capabilities?.catalog.sortKeys ?? []
|
|
12
|
+
const sort = sortFromUrl(Astro.url, capabilities?.catalog)
|
|
13
13
|
|
|
14
14
|
const [productResult, categories] = await Promise.all([
|
|
15
15
|
fetchProducts({ limit: 24, category: slug, sort }),
|
|
16
16
|
fetchCategories(),
|
|
17
17
|
])
|
|
18
18
|
|
|
19
|
-
const products = productResult.items
|
|
19
|
+
const products = productResult.items.map(cardProductOf)
|
|
20
|
+
// The first card's image is the page's LCP, and the preload scanner never reaches it inside the grid's shadow root:
|
|
21
|
+
// the head starts it.
|
|
22
|
+
const firstImage = products[0] === undefined ? null : cardImageOf(products[0]).url
|
|
20
23
|
const category = categories.find((c) => c.slug === slug)
|
|
21
24
|
const categoryName = category?.name ?? slug ?? 'Category'
|
|
22
25
|
const facets = productResult.facets ?? []
|
|
@@ -26,8 +29,9 @@ const facets = productResult.facets ?? []
|
|
|
26
29
|
title={`${categoryName} | Nymbal`}
|
|
27
30
|
description={category?.description ?? `Browse ${categoryName} products.`}
|
|
28
31
|
>
|
|
32
|
+
{firstImage && <link slot="head" rel="preload" as="image" href={firstImage} fetchpriority="high" />}
|
|
29
33
|
<div class="container section">
|
|
30
|
-
<
|
|
34
|
+
<NymbalElement tag="nymbal-breadcrumb" attributes={{ items: JSON.stringify([{ label: 'Home', href: '/' }, { label: categoryName }]) }} />
|
|
31
35
|
|
|
32
36
|
<div class="plp-header">
|
|
33
37
|
<h1 class="plp-title">{categoryName}</h1>
|
|
@@ -36,33 +40,44 @@ const facets = productResult.facets ?? []
|
|
|
36
40
|
{category.description}
|
|
37
41
|
</p>
|
|
38
42
|
)}
|
|
39
|
-
<p class="plp-count" id="product-count">{products.length} products</p>
|
|
40
43
|
</div>
|
|
41
44
|
|
|
42
45
|
<div class="plp-layout">
|
|
43
46
|
<aside class="plp-sidebar">
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
{facets.length > 0 && (
|
|
48
|
+
<nymbal-product-filter
|
|
49
|
+
facets={JSON.stringify(facets)}
|
|
50
|
+
></nymbal-product-filter>
|
|
51
|
+
)}
|
|
47
52
|
</aside>
|
|
48
53
|
|
|
49
54
|
<div class="plp-grid-area">
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
{sortKeys.length > 0 && (
|
|
56
|
+
<div class="plp-sort-bar">
|
|
57
|
+
<label for="sort-select">Sort by</label>
|
|
58
|
+
<nymbal-sort-dropdown
|
|
59
|
+
sort-keys={JSON.stringify(sortKeys)}
|
|
60
|
+
value={sort}
|
|
61
|
+
default-sort={capabilities?.catalog.defaultSort}
|
|
62
|
+
>
|
|
63
|
+
<select id="sort-select" data-testid="sort-dropdown">
|
|
64
|
+
{sortKeys.map((key) => (
|
|
65
|
+
<option value={key} selected={key === sort}>{PRODUCT_SORT_LABELS[key]}</option>
|
|
66
|
+
))}
|
|
67
|
+
</select>
|
|
68
|
+
</nymbal-sort-dropdown>
|
|
62
69
|
</div>
|
|
63
70
|
)}
|
|
64
71
|
|
|
65
|
-
<
|
|
72
|
+
<NymbalElement
|
|
73
|
+
tag="nymbal-product-grid"
|
|
74
|
+
attributes={{
|
|
75
|
+
products: JSON.stringify(products),
|
|
76
|
+
source: 'product-list',
|
|
77
|
+
...(productResult.nextCursor !== null && { 'has-more': '' }),
|
|
78
|
+
'data-category': slug!,
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
66
81
|
</div>
|
|
67
82
|
</div>
|
|
68
83
|
</div>
|
|
@@ -1,100 +1,44 @@
|
|
|
1
1
|
---
|
|
2
|
-
import '
|
|
2
|
+
import NymbalElement from '@nymbalhq/web-components/astro'
|
|
3
3
|
import BaseLayout from '../layouts/BaseLayout.astro'
|
|
4
|
-
import
|
|
5
|
-
|
|
4
|
+
import { fetchCapabilities, fetchCart } from '../lib/api'
|
|
5
|
+
|
|
6
|
+
const [capabilities, cart] = await Promise.all([
|
|
7
|
+
fetchCapabilities(),
|
|
8
|
+
fetchCart(Astro.request.headers.get('cookie')),
|
|
9
|
+
])
|
|
10
|
+
if (capabilities === null) {
|
|
11
|
+
throw new Error('The store did not answer GET /api/capabilities, so checkout cannot tell how to take a payment.')
|
|
12
|
+
}
|
|
13
|
+
const escapeAttribute = (value: string): string =>
|
|
14
|
+
value.replaceAll('&', '&').replaceAll('"', '"').replaceAll('<', '<')
|
|
15
|
+
// The payments adapter's client half, the checkout's light-DOM child in its payment slot; a driver the project
|
|
16
|
+
// registers in src/lib/client.ts renders it. HTML, not markup: Astro would take `slot="payment"` on a component's
|
|
17
|
+
// child as a slot of NymbalElement's own.
|
|
18
|
+
const paymentElement = `<nymbal-payment-element slot="payment" data-testid="payment-element" client="${escapeAttribute(
|
|
19
|
+
JSON.stringify(capabilities.payments.client),
|
|
20
|
+
)}"></nymbal-payment-element>`
|
|
6
21
|
---
|
|
7
22
|
|
|
8
|
-
<BaseLayout title="Checkout | Nymbal" description="Complete your purchase.">
|
|
23
|
+
<BaseLayout title="Checkout | Nymbal" description="Complete your purchase." cart={cart}>
|
|
9
24
|
<div class="container section">
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<CheckoutSteps currentStep="contact" />
|
|
14
|
-
</div>
|
|
15
|
-
|
|
16
|
-
<div class="checkout-error" id="checkout-error" style="display:none;" data-testid="checkout-error" role="alert"></div>
|
|
17
|
-
|
|
18
|
-
<div class="checkout-layout">
|
|
19
|
-
<div class="checkout-form">
|
|
20
|
-
<!-- Contact Section -->
|
|
21
|
-
<section class="checkout-section" id="checkout-contact" data-testid="checkout-contact">
|
|
22
|
-
<h2 class="checkout-section-title">Contact Information</h2>
|
|
23
|
-
<form id="contact-form">
|
|
24
|
-
<div class="form-group">
|
|
25
|
-
<label for="contact-email">Email Address</label>
|
|
26
|
-
<input
|
|
27
|
-
type="email"
|
|
28
|
-
id="contact-email"
|
|
29
|
-
name="email"
|
|
30
|
-
required
|
|
31
|
-
autocomplete="email"
|
|
32
|
-
placeholder="you@example.com"
|
|
33
|
-
data-testid="contact-email"
|
|
34
|
-
/>
|
|
35
|
-
</div>
|
|
36
|
-
<button type="submit" class="btn btn-primary" data-testid="contact-continue">
|
|
37
|
-
Continue to Shipping
|
|
38
|
-
</button>
|
|
39
|
-
</form>
|
|
40
|
-
</section>
|
|
41
|
-
|
|
42
|
-
<!-- Shipping Section -->
|
|
43
|
-
<section class="checkout-section" id="checkout-shipping" style="display:none;" data-testid="checkout-shipping">
|
|
44
|
-
<h2 class="checkout-section-title">Shipping Address</h2>
|
|
45
|
-
<form id="shipping-form">
|
|
46
|
-
<AddressForm id="shipping" />
|
|
47
|
-
<button type="submit" class="btn btn-primary" style="margin-top: var(--nymbal-spacing-md, 1rem);" data-testid="shipping-continue">
|
|
48
|
-
Continue to Payment
|
|
49
|
-
</button>
|
|
50
|
-
</form>
|
|
51
|
-
</section>
|
|
52
|
-
|
|
53
|
-
<!-- Payment Section -->
|
|
54
|
-
<section class="checkout-section" id="checkout-payment" style="display:none;" data-testid="checkout-payment">
|
|
55
|
-
<h2 class="checkout-section-title">Payment</h2>
|
|
56
|
-
<form id="payment-form" novalidate>
|
|
57
|
-
<div class="form-group">
|
|
58
|
-
<label>
|
|
59
|
-
<input type="checkbox" id="billing-same" checked />
|
|
60
|
-
Billing address same as shipping
|
|
61
|
-
</label>
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
<div id="billing-fields" style="display:none;">
|
|
65
|
-
<h3 class="checkout-section-title" style="font-size: 1rem; margin-top: var(--nymbal-spacing-md, 1rem);">Billing Address</h3>
|
|
66
|
-
<div id="billing-form">
|
|
67
|
-
<AddressForm id="billing" />
|
|
68
|
-
</div>
|
|
69
|
-
</div>
|
|
70
|
-
|
|
71
|
-
<div class="payment-section" style="margin-top: var(--nymbal-spacing-md, 1rem);">
|
|
72
|
-
<label style="margin-bottom: var(--nymbal-spacing-sm, 0.5rem);">Card Details</label>
|
|
73
|
-
<div id="stripe-card-element" style="padding: var(--nymbal-spacing-sm, 0.5rem) var(--nymbal-spacing-md, 1rem); border: 1px solid var(--nymbal-color-border, #e4e4e7); border-radius: var(--nymbal-radius-md, 0.5rem); background: var(--nymbal-color-surface, #fff); min-height: 40px;" data-testid="stripe-card-element">
|
|
74
|
-
<!-- Stripe CardElement mounts here -->
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
|
|
78
|
-
<div class="checkout-submit">
|
|
79
|
-
<button type="submit" class="btn btn-primary" data-testid="place-order">
|
|
80
|
-
Place Order
|
|
81
|
-
</button>
|
|
82
|
-
<p class="checkout-terms">
|
|
83
|
-
By placing your order, you agree to our terms and conditions.
|
|
84
|
-
</p>
|
|
85
|
-
</div>
|
|
86
|
-
</form>
|
|
87
|
-
</section>
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
<aside class="checkout-summary" id="checkout-summary" data-testid="checkout-summary">
|
|
91
|
-
<h2 class="checkout-summary-title">Order Summary</h2>
|
|
92
|
-
<p style="font-size: var(--nymbal-font-size-sm, 0.875rem); color: var(--nymbal-color-text-muted, #71717a);">Loading cart...</p>
|
|
93
|
-
</aside>
|
|
94
|
-
</div>
|
|
25
|
+
<NymbalElement tag="nymbal-checkout" attributes={cart === null ? {} : { cart: JSON.stringify(cart) }}>
|
|
26
|
+
<Fragment set:html={paymentElement} />
|
|
27
|
+
</NymbalElement>
|
|
95
28
|
</div>
|
|
96
29
|
</BaseLayout>
|
|
97
30
|
|
|
98
31
|
<script>
|
|
99
|
-
import '
|
|
32
|
+
import { NymbalCheckout } from '@nymbalhq/web-components/elements/nymbal-checkout'
|
|
33
|
+
import { NymbalPaymentElement } from '@nymbalhq/web-components/elements/nymbal-payment-element'
|
|
34
|
+
|
|
35
|
+
// The page's own elements; the layout defines the rest.
|
|
36
|
+
NymbalPaymentElement.define()
|
|
37
|
+
NymbalCheckout.define()
|
|
38
|
+
|
|
39
|
+
// The element places the order; the page only goes to it.
|
|
40
|
+
document.addEventListener('nymbal:checkout:placed', (event) => {
|
|
41
|
+
const { orderNumber } = (event as CustomEvent<{ orderNumber: string }>).detail
|
|
42
|
+
window.location.href = `/order/${encodeURIComponent(orderNumber)}`
|
|
43
|
+
})
|
|
100
44
|
</script>
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
2
|
+
import NymbalElement from '@nymbalhq/web-components/astro'
|
|
3
|
+
import { FOOTER } from '../../lib/chrome'
|
|
3
4
|
Astro.response.headers.set('Cache-Control', 'public, s-maxage=86400, stale-while-revalidate=300')
|
|
5
|
+
// A fragment has no <meta charset>, so the charset is in the header.
|
|
6
|
+
Astro.response.headers.set('Content-Type', 'text/html; charset=utf-8')
|
|
4
7
|
---
|
|
5
|
-
<
|
|
8
|
+
<NymbalElement tag="nymbal-site-footer" attributes={FOOTER} />
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
2
|
+
import NymbalElement from '@nymbalhq/web-components/astro'
|
|
3
|
+
import { HEADER, headerSlots } from '../../lib/chrome'
|
|
4
|
+
import { headerFragment } from '../../lib/fragments'
|
|
5
|
+
|
|
6
|
+
const header = await headerFragment()
|
|
7
|
+
if (header instanceof Response) return header
|
|
8
|
+
|
|
3
9
|
Astro.response.headers.set('Cache-Control', 'public, s-maxage=3600, stale-while-revalidate=300')
|
|
10
|
+
// A fragment has no <meta charset>, so the charset is in the header.
|
|
11
|
+
Astro.response.headers.set('Content-Type', 'text/html; charset=utf-8')
|
|
4
12
|
---
|
|
5
|
-
<
|
|
13
|
+
<NymbalElement tag="nymbal-site-header" attributes={HEADER}>
|
|
14
|
+
<Fragment set:html={headerSlots(header.canSearch)} />
|
|
15
|
+
</NymbalElement>
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import NymbalElement from '@nymbalhq/web-components/astro'
|
|
3
|
+
import { navFragment } from '../../lib/fragments'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const nav = await navFragment()
|
|
6
|
+
if (nav instanceof Response) return nav
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
Astro.response.headers.set('Cache-Control', 'public, s-maxage=21600, stale-while-revalidate=300')
|
|
9
|
+
// A fragment has no <meta charset>, so the charset is in the header.
|
|
10
|
+
Astro.response.headers.set('Content-Type', 'text/html; charset=utf-8')
|
|
8
11
|
---
|
|
9
|
-
<
|
|
12
|
+
<NymbalElement tag="nymbal-category-nav" attributes={{ categories: JSON.stringify(nav.categories) }} />
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import NymbalElement from '@nymbalhq/web-components/astro'
|
|
3
|
+
import { cardProductOf } from '@nymbalhq/web-components/ssr'
|
|
4
|
+
import { productFragment } from '../../../lib/fragments'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const { slug } = Astro.params
|
|
8
|
-
const product = await fetchProduct(slug!)
|
|
6
|
+
const card = await productFragment(Astro.params.slug!)
|
|
7
|
+
if (card instanceof Response) return card
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
Astro.response.headers.set('Cache-Control', 'public, s-maxage=300, stale-while-revalidate=60')
|
|
10
|
+
// A fragment has no <meta charset>, so the charset is in the header.
|
|
11
|
+
Astro.response.headers.set('Content-Type', 'text/html; charset=utf-8')
|
|
13
12
|
---
|
|
14
|
-
<
|
|
13
|
+
<NymbalElement tag="nymbal-product-card" attributes={{ product: JSON.stringify(cardProductOf(card.product)) }} />
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
import '../styles/pages/home.css'
|
|
3
|
+
import NymbalElement from '@nymbalhq/web-components/astro'
|
|
4
|
+
import { cardImageOf, cardProductOf } from '@nymbalhq/web-components/ssr'
|
|
3
5
|
import BaseLayout from '../layouts/BaseLayout.astro'
|
|
4
6
|
import HeroSection from '../components/HeroSection.astro'
|
|
5
|
-
import CategoryNav from '../components/CategoryNav.astro'
|
|
6
|
-
import ProductCard from '../components/ProductCard.astro'
|
|
7
7
|
import NewsletterSignup from '../components/NewsletterSignup.astro'
|
|
8
8
|
import { fetchProducts, fetchCategories } from '../lib/api'
|
|
9
9
|
import { organizationJsonLd } from '../lib/seo'
|
|
@@ -12,17 +12,21 @@ const [productResult, categories] = await Promise.all([
|
|
|
12
12
|
fetchProducts({ limit: 8 }),
|
|
13
13
|
fetchCategories(),
|
|
14
14
|
])
|
|
15
|
+
const products = productResult.items.map(cardProductOf)
|
|
16
|
+
// The first card's image is the page's likely LCP, and the preload scanner never reaches it inside the grid's shadow
|
|
17
|
+
// root: the head starts it.
|
|
18
|
+
const firstImage = products[0] === undefined ? null : cardImageOf(products[0]).url
|
|
15
19
|
|
|
16
|
-
const products = productResult.items
|
|
17
20
|
const jsonLd = organizationJsonLd('Nymbal', Astro.url.origin)
|
|
18
21
|
---
|
|
19
22
|
|
|
20
23
|
<BaseLayout title="Nymbal — Modern Storefront" jsonLd={jsonLd}>
|
|
24
|
+
{firstImage && <link slot="head" rel="preload" as="image" href={firstImage} fetchpriority="high" />}
|
|
21
25
|
<HeroSection />
|
|
22
26
|
|
|
23
27
|
<section class="section container">
|
|
24
28
|
<h2 class="section-title">Shop by Category</h2>
|
|
25
|
-
<
|
|
29
|
+
<NymbalElement tag="nymbal-category-nav" attributes={{ categories: JSON.stringify(categories) }} />
|
|
26
30
|
</section>
|
|
27
31
|
|
|
28
32
|
<section class="featured-section section">
|
|
@@ -31,19 +35,18 @@ const jsonLd = organizationJsonLd('Nymbal', Astro.url.origin)
|
|
|
31
35
|
<h2 class="section-title" style="margin-bottom: 0;">Featured Products</h2>
|
|
32
36
|
<a href="/products" class="featured-link">View all</a>
|
|
33
37
|
</div>
|
|
34
|
-
{products.
|
|
35
|
-
<div class="product-grid">
|
|
36
|
-
{products.map((product) => (
|
|
37
|
-
<ProductCard product={product} />
|
|
38
|
-
))}
|
|
39
|
-
</div>
|
|
40
|
-
) : (
|
|
41
|
-
<p style="text-align: center; color: var(--nymbal-color-text-muted, #71717a);">
|
|
42
|
-
Products coming soon.
|
|
43
|
-
</p>
|
|
44
|
-
)}
|
|
38
|
+
<NymbalElement tag="nymbal-product-grid" attributes={{ products: JSON.stringify(products) }} />
|
|
45
39
|
</div>
|
|
46
40
|
</section>
|
|
47
41
|
|
|
48
42
|
<NewsletterSignup />
|
|
49
43
|
</BaseLayout>
|
|
44
|
+
|
|
45
|
+
<script>
|
|
46
|
+
import '../lib/client.js'
|
|
47
|
+
import { NymbalCategoryNav } from '@nymbalhq/web-components/elements/nymbal-category-nav'
|
|
48
|
+
import { NymbalProductGrid } from '@nymbalhq/web-components/elements/nymbal-product-grid'
|
|
49
|
+
|
|
50
|
+
NymbalCategoryNav.define()
|
|
51
|
+
NymbalProductGrid.define()
|
|
52
|
+
</script>
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
import '../../styles/pages/confirmation.css'
|
|
3
3
|
import BaseLayout from '../../layouts/BaseLayout.astro'
|
|
4
|
-
import
|
|
4
|
+
import NymbalElement from '@nymbalhq/web-components/astro'
|
|
5
5
|
import { fetchOrder } from '../../lib/api'
|
|
6
|
-
import { formatPrice } from '../../lib/format'
|
|
7
6
|
import { isPaid, orderHeading } from '../../lib/order-status'
|
|
8
7
|
|
|
9
8
|
const { orderNumber } = Astro.params
|
|
@@ -11,11 +10,22 @@ const order = await fetchOrder(orderNumber!)
|
|
|
11
10
|
|
|
12
11
|
if (!order) return Astro.redirect('/404')
|
|
13
12
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
const summary = {
|
|
14
|
+
items: JSON.stringify(
|
|
15
|
+
(order.lineItems ?? []).map((item) => ({
|
|
16
|
+
name: `${item.productName}${item.variantName ? ` - ${item.variantName}` : ''}`,
|
|
17
|
+
qty: item.qty,
|
|
18
|
+
unitPriceMinor: item.unitPriceMinor,
|
|
19
|
+
lineTotalMinor: item.lineTotalMinor,
|
|
20
|
+
})),
|
|
21
|
+
),
|
|
22
|
+
'subtotal-minor': String(order.subtotalMinor),
|
|
23
|
+
'discount-minor': String(order.discountTotalMinor ?? 0),
|
|
24
|
+
'shipping-minor': String(order.shippingTotalMinor ?? 0),
|
|
25
|
+
'tax-minor': String(order.taxTotalMinor ?? 0),
|
|
26
|
+
'total-minor': String(order.totalMinor),
|
|
27
|
+
currency: order.currency,
|
|
28
|
+
}
|
|
19
29
|
|
|
20
30
|
const shippingAddress = order.shippingAddress
|
|
21
31
|
const heading = orderHeading(order.status)
|
|
@@ -63,45 +73,10 @@ const paid = isPaid(order.status)
|
|
|
63
73
|
<dd style="text-transform: capitalize;" data-testid="order-status">{order.status}</dd>
|
|
64
74
|
</div>
|
|
65
75
|
</dl>
|
|
66
|
-
|
|
67
|
-
<div class="confirmation-items">
|
|
68
|
-
{lineItems.map((item) => (
|
|
69
|
-
<div class="confirmation-item">
|
|
70
|
-
<span>
|
|
71
|
-
<span class="confirmation-item-name">{item.name}</span>
|
|
72
|
-
<span class="confirmation-item-qty"> x{item.qty}</span>
|
|
73
|
-
</span>
|
|
74
|
-
<span class="confirmation-item-price">
|
|
75
|
-
{formatPrice(item.priceMinor * item.qty, order.currency)}
|
|
76
|
-
</span>
|
|
77
|
-
</div>
|
|
78
|
-
))}
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
|
-
<div class="confirmation-totals">
|
|
82
|
-
<div class="confirmation-total-row">
|
|
83
|
-
<span>Subtotal</span>
|
|
84
|
-
<span>{formatPrice(order.subtotalMinor, order.currency)}</span>
|
|
85
|
-
</div>
|
|
86
|
-
{order.shippingTotalMinor != null && (
|
|
87
|
-
<div class="confirmation-total-row">
|
|
88
|
-
<span>Shipping</span>
|
|
89
|
-
<span>{order.shippingTotalMinor === 0 ? 'Free' : formatPrice(order.shippingTotalMinor, order.currency)}</span>
|
|
90
|
-
</div>
|
|
91
|
-
)}
|
|
92
|
-
{order.taxTotalMinor != null && (
|
|
93
|
-
<div class="confirmation-total-row">
|
|
94
|
-
<span>Tax</span>
|
|
95
|
-
<span>{formatPrice(order.taxTotalMinor, order.currency)}</span>
|
|
96
|
-
</div>
|
|
97
|
-
)}
|
|
98
|
-
<div class="confirmation-total-row grand-total">
|
|
99
|
-
<span>Total</span>
|
|
100
|
-
<span>{formatPrice(order.totalMinor, order.currency)}</span>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
76
|
</div>
|
|
104
77
|
|
|
78
|
+
<NymbalElement tag="nymbal-order-summary" attributes={{ class: 'confirmation-summary', ...summary }} />
|
|
79
|
+
|
|
105
80
|
{shippingAddress && (
|
|
106
81
|
<div class="confirmation-card">
|
|
107
82
|
<h2 class="confirmation-card-title">Shipping Address</h2>
|
|
@@ -124,4 +99,7 @@ const paid = isPaid(order.status)
|
|
|
124
99
|
|
|
125
100
|
<script>
|
|
126
101
|
import '../../scripts/order-status.js'
|
|
102
|
+
import { NymbalOrderSummary } from '@nymbalhq/web-components/elements/nymbal-order-summary'
|
|
103
|
+
|
|
104
|
+
NymbalOrderSummary.define()
|
|
127
105
|
</script>
|