create-nymbal-app 0.4.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.
Files changed (163) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +7 -4
  3. package/dist/index.js.map +1 -1
  4. package/package.json +5 -4
  5. package/scaffold/project/astro/src/pages/index.astro +13 -15
  6. package/scaffold/templates/astro/_dot.node-version +1 -1
  7. package/scaffold/templates/astro/astro.config.mjs +23 -0
  8. package/scaffold/templates/astro/package.json +26 -26
  9. package/scaffold/templates/astro/src/components/SkipNav.astro +3 -1
  10. package/scaffold/templates/astro/src/elements/index.ts +11 -0
  11. package/scaffold/templates/astro/src/layouts/BaseLayout.astro +15 -4
  12. package/scaffold/templates/astro/src/lib/api.ts +113 -21
  13. package/scaffold/templates/astro/src/lib/chrome.ts +59 -0
  14. package/scaffold/templates/astro/src/lib/client.ts +4 -4
  15. package/scaffold/templates/astro/src/lib/fragments.ts +48 -0
  16. package/scaffold/templates/astro/src/lib/server-elements.ts +5 -0
  17. package/scaffold/templates/astro/src/middleware.ts +37 -0
  18. package/scaffold/templates/astro/src/pages/account/addresses.astro +5 -97
  19. package/scaffold/templates/astro/src/pages/account/login.astro +7 -34
  20. package/scaffold/templates/astro/src/pages/account/profile.astro +6 -56
  21. package/scaffold/templates/astro/src/pages/account/register.astro +7 -73
  22. package/scaffold/templates/astro/src/pages/cart.astro +14 -7
  23. package/scaffold/templates/astro/src/pages/category/[slug].astro +17 -21
  24. package/scaffold/templates/astro/src/pages/checkout.astro +30 -94
  25. package/scaffold/templates/astro/src/pages/fragments/footer.astro +5 -2
  26. package/scaffold/templates/astro/src/pages/fragments/header.astro +12 -2
  27. package/scaffold/templates/astro/src/pages/fragments/nav.astro +8 -5
  28. package/scaffold/templates/astro/src/pages/fragments/product/[slug].astro +9 -10
  29. package/scaffold/templates/astro/src/pages/index.astro +18 -15
  30. package/scaffold/templates/astro/src/pages/order/[orderNumber].astro +22 -44
  31. package/scaffold/templates/astro/src/pages/product/[slug].astro +44 -30
  32. package/scaffold/templates/astro/src/pages/products/index.astro +16 -21
  33. package/scaffold/templates/astro/src/pages/search.astro +10 -11
  34. package/scaffold/templates/astro/src/scripts/init-client.ts +14 -0
  35. package/scaffold/templates/astro/src/scripts/pdp.ts +24 -21
  36. package/scaffold/templates/astro/src/scripts/plp.ts +15 -93
  37. package/scaffold/templates/astro/src/scripts/search.ts +7 -132
  38. package/scaffold/templates/astro/src/scripts/signed-in-redirect.ts +4 -0
  39. package/scaffold/templates/astro/src/styles/global.css +49 -45
  40. package/scaffold/templates/astro/src/styles/pages/account.css +0 -90
  41. package/scaffold/templates/astro/src/styles/pages/confirmation.css +6 -0
  42. package/scaffold/templates/astro/src/styles/pages/home.css +9 -100
  43. package/scaffold/templates/astro/src/styles/pages/pdp.css +5 -171
  44. package/scaffold/templates/astro/src/styles/pages/plp.css +3 -42
  45. package/scaffold/templates/astro/src/styles/pages/search.css +4 -110
  46. package/scaffold/templates/astro/src/styles/tokens.css +56 -0
  47. package/scaffold/templates/nextjs/_dot.node-version +1 -1
  48. package/scaffold/templates/nextjs/app/(catalog)/category/[slug]/page.tsx +74 -0
  49. package/scaffold/templates/nextjs/app/(catalog)/home/[key]/page.tsx +80 -0
  50. package/scaffold/templates/nextjs/app/(catalog)/layout.tsx +13 -0
  51. package/scaffold/templates/nextjs/app/(catalog)/not-found.tsx +7 -0
  52. package/scaffold/templates/nextjs/app/(catalog)/product/[slug]/page.tsx +182 -0
  53. package/scaffold/templates/nextjs/app/(catalog)/products/page.tsx +62 -0
  54. package/scaffold/templates/nextjs/app/(catalog)/search/SearchView.tsx +90 -0
  55. package/scaffold/templates/nextjs/app/(catalog)/search/page.tsx +46 -0
  56. package/scaffold/templates/nextjs/app/(shopper)/account/addresses/AddressesView.tsx +5 -0
  57. package/scaffold/templates/nextjs/app/(shopper)/account/addresses/page.tsx +17 -0
  58. package/scaffold/templates/nextjs/app/(shopper)/account/login/LoginView.tsx +28 -0
  59. package/scaffold/templates/nextjs/app/(shopper)/account/login/page.tsx +23 -0
  60. package/scaffold/templates/nextjs/app/(shopper)/account/profile/ProfileView.tsx +5 -0
  61. package/scaffold/templates/nextjs/app/(shopper)/account/profile/page.tsx +20 -0
  62. package/scaffold/templates/nextjs/app/(shopper)/account/register/RegisterView.tsx +26 -0
  63. package/scaffold/templates/nextjs/app/(shopper)/account/register/page.tsx +11 -0
  64. package/scaffold/templates/nextjs/app/(shopper)/cart/CartView.tsx +5 -0
  65. package/scaffold/templates/nextjs/app/(shopper)/cart/page.tsx +22 -0
  66. package/scaffold/templates/nextjs/app/(shopper)/checkout/CheckoutView.tsx +27 -0
  67. package/scaffold/templates/nextjs/app/(shopper)/checkout/page.tsx +39 -0
  68. package/scaffold/templates/nextjs/app/(shopper)/layout.tsx +12 -0
  69. package/scaffold/templates/nextjs/app/{order → (shopper)/order}/[orderNumber]/page.tsx +24 -5
  70. package/scaffold/templates/nextjs/app/error.tsx +3 -7
  71. package/scaffold/templates/nextjs/app/fragments/footer/route.ts +13 -0
  72. package/scaffold/templates/nextjs/app/fragments/header/[key]/route.ts +18 -0
  73. package/scaffold/templates/nextjs/app/fragments/nav/[key]/route.ts +23 -0
  74. package/scaffold/templates/nextjs/app/fragments/product/[slug]/route.ts +22 -0
  75. package/scaffold/templates/nextjs/app/globals.css +5 -23
  76. package/scaffold/templates/nextjs/app/layout.tsx +8 -5
  77. package/scaffold/templates/nextjs/app/not-found.tsx +9 -33
  78. package/scaffold/templates/nextjs/app/tokens.css +56 -0
  79. package/scaffold/templates/nextjs/components/client/ListingControls.tsx +87 -0
  80. package/scaffold/templates/nextjs/components/client/NymbalSetup.tsx +48 -6
  81. package/scaffold/templates/nextjs/components/client/ProductInteractive.tsx +30 -15
  82. package/scaffold/templates/nextjs/components/client/elements.tsx +15 -0
  83. package/scaffold/templates/nextjs/components/server/Header.tsx +33 -102
  84. package/scaffold/templates/nextjs/components/server/NotFoundView.tsx +39 -0
  85. package/scaffold/templates/nextjs/components/server/SkipNav.tsx +7 -3
  86. package/scaffold/templates/nextjs/elements/index.ts +12 -0
  87. package/scaffold/templates/nextjs/lib/api.ts +151 -55
  88. package/scaffold/templates/nextjs/lib/chrome.ts +40 -0
  89. package/scaffold/templates/nextjs/lib/fragments.ts +16 -0
  90. package/scaffold/templates/nextjs/lib/header.ts +39 -0
  91. package/scaffold/templates/nextjs/lib/render.ts +14 -0
  92. package/scaffold/templates/nextjs/next.config.mjs +32 -0
  93. package/scaffold/templates/nextjs/package.json +28 -27
  94. package/scaffold/templates/nextjs/styles/pages/account.module.css +0 -70
  95. package/scaffold/templates/nextjs/styles/pages/confirmation.module.css +6 -85
  96. package/scaffold/templates/nextjs/styles/pages/home.module.css +2 -103
  97. package/scaffold/templates/nextjs/styles/pages/pdp.module.css +3 -183
  98. package/scaffold/templates/nextjs/styles/pages/plp.module.css +9 -143
  99. package/scaffold/templates/nextjs/styles/pages/search.module.css +1 -145
  100. package/scaffold/templates/nextjs/tsconfig.dist.json +5 -0
  101. package/scaffold/templates/astro/src/components/AddressForm.astro +0 -107
  102. package/scaffold/templates/astro/src/components/Breadcrumb.astro +0 -20
  103. package/scaffold/templates/astro/src/components/CategoryNav.astro +0 -27
  104. package/scaffold/templates/astro/src/components/CheckoutSteps.astro +0 -29
  105. package/scaffold/templates/astro/src/components/Footer.astro +0 -108
  106. package/scaffold/templates/astro/src/components/Header.astro +0 -198
  107. package/scaffold/templates/astro/src/components/ImageGallery.astro +0 -42
  108. package/scaffold/templates/astro/src/components/OrderSummary.astro +0 -53
  109. package/scaffold/templates/astro/src/components/Pagination.astro +0 -13
  110. package/scaffold/templates/astro/src/components/PriceDisplay.astro +0 -25
  111. package/scaffold/templates/astro/src/components/ProductCard.astro +0 -44
  112. package/scaffold/templates/astro/src/components/RelatedProducts.astro +0 -28
  113. package/scaffold/templates/astro/src/components/ReviewForm.astro +0 -74
  114. package/scaffold/templates/astro/src/components/ReviewList.astro +0 -41
  115. package/scaffold/templates/astro/src/components/StockBadge.astro +0 -15
  116. package/scaffold/templates/astro/src/scripts/auth-forms.ts +0 -82
  117. package/scaffold/templates/astro/src/scripts/cart-page.ts +0 -118
  118. package/scaffold/templates/astro/src/scripts/checkout.ts +0 -271
  119. package/scaffold/templates/astro/src/scripts/image-gallery.ts +0 -22
  120. package/scaffold/templates/astro/src/scripts/profile-form.ts +0 -57
  121. package/scaffold/templates/astro/src/scripts/review-form.ts +0 -27
  122. package/scaffold/templates/astro/src/styles/pages/cart.css +0 -215
  123. package/scaffold/templates/astro/src/styles/pages/checkout.css +0 -270
  124. package/scaffold/templates/nextjs/app/account/addresses/page.tsx +0 -10
  125. package/scaffold/templates/nextjs/app/account/login/page.tsx +0 -23
  126. package/scaffold/templates/nextjs/app/account/profile/page.tsx +0 -10
  127. package/scaffold/templates/nextjs/app/account/register/page.tsx +0 -10
  128. package/scaffold/templates/nextjs/app/cart/page.tsx +0 -10
  129. package/scaffold/templates/nextjs/app/category/[slug]/page.tsx +0 -61
  130. package/scaffold/templates/nextjs/app/checkout/page.tsx +0 -17
  131. package/scaffold/templates/nextjs/app/page.tsx +0 -63
  132. package/scaffold/templates/nextjs/app/product/[slug]/page.tsx +0 -111
  133. package/scaffold/templates/nextjs/app/products/page.tsx +0 -40
  134. package/scaffold/templates/nextjs/app/search/page.tsx +0 -24
  135. package/scaffold/templates/nextjs/components/client/AddressManager.tsx +0 -181
  136. package/scaffold/templates/nextjs/components/client/CartPage.tsx +0 -121
  137. package/scaffold/templates/nextjs/components/client/CheckoutForm.tsx +0 -482
  138. package/scaffold/templates/nextjs/components/client/ImageGallery.tsx +0 -68
  139. package/scaffold/templates/nextjs/components/client/LoginForm.tsx +0 -89
  140. package/scaffold/templates/nextjs/components/client/MiniCartWrapper.tsx +0 -12
  141. package/scaffold/templates/nextjs/components/client/ProductGrid.tsx +0 -137
  142. package/scaffold/templates/nextjs/components/client/ProfileForm.tsx +0 -112
  143. package/scaffold/templates/nextjs/components/client/RegisterForm.tsx +0 -131
  144. package/scaffold/templates/nextjs/components/client/ReviewForm.tsx +0 -116
  145. package/scaffold/templates/nextjs/components/client/SearchBarWrapper.tsx +0 -27
  146. package/scaffold/templates/nextjs/components/client/SearchResults.tsx +0 -181
  147. package/scaffold/templates/nextjs/components/server/Breadcrumb.tsx +0 -36
  148. package/scaffold/templates/nextjs/components/server/CategoryNav.tsx +0 -29
  149. package/scaffold/templates/nextjs/components/server/CheckoutSteps.tsx +0 -60
  150. package/scaffold/templates/nextjs/components/server/Footer.module.css +0 -64
  151. package/scaffold/templates/nextjs/components/server/Footer.tsx +0 -79
  152. package/scaffold/templates/nextjs/components/server/Header.module.css +0 -147
  153. package/scaffold/templates/nextjs/components/server/OrderSummary.tsx +0 -79
  154. package/scaffold/templates/nextjs/components/server/PriceDisplay.tsx +0 -31
  155. package/scaffold/templates/nextjs/components/server/ProductCard.tsx +0 -59
  156. package/scaffold/templates/nextjs/components/server/RelatedProducts.tsx +0 -31
  157. package/scaffold/templates/nextjs/components/server/ReviewList.tsx +0 -62
  158. package/scaffold/templates/nextjs/styles/pages/cart.module.css +0 -240
  159. package/scaffold/templates/nextjs/styles/pages/checkout.module.css +0 -404
  160. /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/layout.tsx +0 -0
  161. /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/orders/[orderNumber]/page.tsx +0 -0
  162. /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/orders/page.tsx +0 -0
  163. /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/page.tsx +0 -0
@@ -1,137 +0,0 @@
1
- 'use client'
2
-
3
- import { useEffect, useState, useCallback } from 'react'
4
- import type { DenormalisedProduct } from '@nymbalhq/sdk'
5
- import type { Facet, ProductSortKey } from '@nymbalhq/types'
6
- import { ProductFilter, SortDropdown, useProductList } from '@nymbalhq/react'
7
- import { ProductCard } from '@/components/server/ProductCard'
8
- import styles from '@/styles/pages/plp.module.css'
9
-
10
- interface ProductGridProps {
11
- initialProducts: DenormalisedProduct[]
12
- initialCursor: string | null
13
- initialSort: ProductSortKey
14
- /** The server-rendered listing's facets: none when the store's search cannot facet, and then no filter. */
15
- initialFacets: Facet[]
16
- /** The sort keys the store reports, in order, and the one it lists by when none is asked. */
17
- sortKeys: ProductSortKey[]
18
- defaultSort?: ProductSortKey | undefined
19
- categorySlug?: string
20
- }
21
-
22
- export function ProductGrid({
23
- initialProducts,
24
- initialCursor,
25
- initialSort,
26
- initialFacets,
27
- sortKeys,
28
- defaultSort,
29
- categorySlug,
30
- }: ProductGridProps) {
31
- const { products, pagination, loading, sort, load, loadMore, setSort } = useProductList()
32
- const [initialized, setInitialized] = useState(false)
33
- const [showMobileFilters, setShowMobileFilters] = useState(false)
34
-
35
- useEffect(() => {
36
- load({ limit: 24, category: categorySlug, sort: initialSort })
37
- setInitialized(true)
38
- }, [categorySlug, initialSort, load])
39
-
40
- // The URL carries the sort, so a reload or a shared link renders the same order server-side.
41
- const handleSort = useCallback(
42
- (event: CustomEvent<{ sort: string }>) => {
43
- const key = event.detail.sort as ProductSortKey
44
- const url = new URL(window.location.href)
45
- url.searchParams.set('sort', key)
46
- window.history.replaceState(window.history.state, '', url)
47
- void setSort(key)
48
- },
49
- [setSort],
50
- )
51
-
52
- const displayProducts = initialized && products.length > 0 ? products : initialProducts
53
- const hasMore = initialized ? pagination.hasMore : !!initialCursor
54
-
55
- const handleLoadMore = useCallback(() => {
56
- if (hasMore && !loading) {
57
- loadMore()
58
- }
59
- }, [hasMore, loading, loadMore])
60
-
61
- return (
62
- <div className={styles.layout}>
63
- <aside className={styles.sidebar}>
64
- {initialFacets.length > 0 && (
65
- <>
66
- <button
67
- className={styles.mobileFilterToggle}
68
- onClick={() => setShowMobileFilters((v) => !v)}
69
- type="button"
70
- >
71
- {showMobileFilters ? 'Hide Filters' : 'Show Filters'}
72
- </button>
73
- <div
74
- className={styles.filterSection}
75
- style={{ display: showMobileFilters ? 'block' : undefined }}
76
- >
77
- <ProductFilter facets={initialFacets} />
78
- </div>
79
- </>
80
- )}
81
- </aside>
82
-
83
- <div className={styles.content}>
84
- <div className={styles.sortBar}>
85
- <span className={styles.resultCount}>
86
- {displayProducts.length} product{displayProducts.length !== 1 ? 's' : ''}
87
- </span>
88
- {sortKeys.length > 0 && (
89
- <SortDropdown
90
- className={styles.sortSelect}
91
- sortKeys={sortKeys}
92
- value={initialized ? sort : initialSort}
93
- defaultSort={defaultSort}
94
- onSortChange={handleSort}
95
- />
96
- )}
97
- </div>
98
-
99
- {displayProducts.length > 0 ? (
100
- <div className="product-grid">
101
- {displayProducts.map((product) => (
102
- <ProductCard
103
- key={product.id}
104
- product={{
105
- slug: product.slug,
106
- name: product.name,
107
- priceMinor: product.priceMinor ?? null,
108
- priceRange: product.priceRange ?? null,
109
- currency: product.currency ?? null,
110
- media: product.media,
111
- }}
112
- />
113
- ))}
114
- </div>
115
- ) : (
116
- <div className={styles.empty}>
117
- <p>No products found.</p>
118
- </div>
119
- )}
120
-
121
- {hasMore && (
122
- <div className={styles.pagination}>
123
- <button
124
- type="button"
125
- className="btn btn-secondary"
126
- onClick={handleLoadMore}
127
- disabled={loading}
128
- data-testid="load-more"
129
- >
130
- {loading ? 'Loading...' : 'Load More'}
131
- </button>
132
- </div>
133
- )}
134
- </div>
135
- </div>
136
- )
137
- }
@@ -1,112 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useEffect, useCallback, type FormEvent } from 'react'
4
- import { useAuth } from '@nymbalhq/react'
5
- import styles from '@/styles/pages/account.module.css'
6
-
7
- export function ProfileForm() {
8
- const { customer, updateProfile } = useAuth()
9
-
10
- const [firstName, setFirstName] = useState('')
11
- const [lastName, setLastName] = useState('')
12
- const [phone, setPhone] = useState('')
13
- const [saving, setSaving] = useState(false)
14
- const [success, setSuccess] = useState(false)
15
- const [error, setError] = useState<string | null>(null)
16
-
17
- useEffect(() => {
18
- if (customer) {
19
- setFirstName(customer.firstName ?? '')
20
- setLastName(customer.lastName ?? '')
21
- setPhone(customer.phone ?? '')
22
- }
23
- }, [customer])
24
-
25
- const handleSubmit = useCallback(
26
- async (e: FormEvent) => {
27
- e.preventDefault()
28
- setSaving(true)
29
- setError(null)
30
- setSuccess(false)
31
-
32
- try {
33
- await updateProfile({ firstName, lastName, phone })
34
- setSuccess(true)
35
- } catch (err) {
36
- setError(err instanceof Error ? err.message : 'Failed to update profile.')
37
- } finally {
38
- setSaving(false)
39
- }
40
- },
41
- [firstName, lastName, phone, updateProfile],
42
- )
43
-
44
- return (
45
- <div>
46
- <h2 className={styles.pageTitle}>Profile</h2>
47
-
48
- {success && (
49
- <div className={`${styles.alert} ${styles.alertSuccess}`}>
50
- Profile updated successfully.
51
- </div>
52
- )}
53
- {error && <div className={`${styles.alert} ${styles.alertError}`}>{error}</div>}
54
-
55
- <div className={styles.formCard}>
56
- <h3 className={styles.formCardTitle}>Personal Information</h3>
57
- <form onSubmit={handleSubmit}>
58
- <div className={styles.formGrid}>
59
- <div>
60
- <label htmlFor="profile-first-name">First Name</label>
61
- <input
62
- id="profile-first-name"
63
- data-testid="profile-firstName"
64
- type="text"
65
- value={firstName}
66
- onChange={(e) => setFirstName(e.target.value)}
67
- autoComplete="given-name"
68
- />
69
- </div>
70
- <div>
71
- <label htmlFor="profile-last-name">Last Name</label>
72
- <input
73
- id="profile-last-name"
74
- data-testid="profile-lastName"
75
- type="text"
76
- value={lastName}
77
- onChange={(e) => setLastName(e.target.value)}
78
- autoComplete="family-name"
79
- />
80
- </div>
81
- <div className={styles.formGridFull}>
82
- <label htmlFor="profile-email">Email</label>
83
- <input
84
- id="profile-email"
85
- type="email"
86
- value={customer?.email ?? ''}
87
- disabled
88
- autoComplete="email"
89
- />
90
- </div>
91
- <div className={styles.formGridFull}>
92
- <label htmlFor="profile-phone">Phone</label>
93
- <input
94
- id="profile-phone"
95
- type="tel"
96
- value={phone}
97
- onChange={(e) => setPhone(e.target.value)}
98
- placeholder="Your phone number"
99
- autoComplete="tel"
100
- />
101
- </div>
102
- </div>
103
- <div className={styles.formActions}>
104
- <button type="submit" className={styles.saveButton} disabled={saving}>
105
- {saving ? 'Saving...' : 'Save Changes'}
106
- </button>
107
- </div>
108
- </form>
109
- </div>
110
- </div>
111
- )
112
- }
@@ -1,131 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useCallback, type FormEvent } from 'react'
4
- import { useRouter } from 'next/navigation'
5
- import Link from 'next/link'
6
- import { useAuth } from '@nymbalhq/react'
7
- import styles from '@/styles/pages/account.module.css'
8
-
9
- export function RegisterForm() {
10
- const router = useRouter()
11
- const { register } = useAuth()
12
-
13
- const [firstName, setFirstName] = useState('')
14
- const [lastName, setLastName] = useState('')
15
- const [email, setEmail] = useState('')
16
- const [password, setPassword] = useState('')
17
- const [confirmPassword, setConfirmPassword] = useState('')
18
- const [error, setError] = useState<string | null>(null)
19
- const [loading, setLoading] = useState(false)
20
-
21
- const handleSubmit = useCallback(
22
- async (e: FormEvent) => {
23
- e.preventDefault()
24
- setError(null)
25
-
26
- if (password !== confirmPassword) {
27
- setError('Passwords do not match.')
28
- return
29
- }
30
-
31
- setLoading(true)
32
-
33
- try {
34
- await register({ firstName, lastName, email, password })
35
- router.push('/account')
36
- } catch (err) {
37
- setError(err instanceof Error ? err.message : 'Registration failed. Please try again.')
38
- setLoading(false)
39
- }
40
- },
41
- [firstName, lastName, email, password, confirmPassword, register, router],
42
- )
43
-
44
- return (
45
- <div className={styles.authPage}>
46
- <div className={styles.authCard} data-testid="register-form">
47
- <h1 className={styles.authTitle}>Create Account</h1>
48
- <p className={styles.authSubtitle}>Join us and start shopping.</p>
49
-
50
- {error && <div className={styles.alertError + ' ' + styles.alert}>{error}</div>}
51
-
52
- <form onSubmit={handleSubmit} className={styles.authForm}>
53
- <div className="form-group">
54
- <label htmlFor="register-first-name">First Name</label>
55
- <input
56
- id="register-first-name"
57
- type="text"
58
- value={firstName}
59
- onChange={(e) => setFirstName(e.target.value)}
60
- required
61
- placeholder="First name"
62
- autoComplete="given-name"
63
- />
64
- </div>
65
- <div className="form-group">
66
- <label htmlFor="register-last-name">Last Name</label>
67
- <input
68
- id="register-last-name"
69
- type="text"
70
- value={lastName}
71
- onChange={(e) => setLastName(e.target.value)}
72
- required
73
- placeholder="Last name"
74
- autoComplete="family-name"
75
- />
76
- </div>
77
- <div className="form-group">
78
- <label htmlFor="register-email">Email</label>
79
- <input
80
- id="register-email"
81
- type="email"
82
- value={email}
83
- onChange={(e) => setEmail(e.target.value)}
84
- required
85
- data-testid="register-email"
86
- placeholder="you@example.com"
87
- autoComplete="email"
88
- />
89
- </div>
90
- <div className="form-group">
91
- <label htmlFor="register-password">Password</label>
92
- <input
93
- id="register-password"
94
- type="password"
95
- value={password}
96
- onChange={(e) => setPassword(e.target.value)}
97
- required
98
- placeholder="Create a password"
99
- autoComplete="new-password"
100
- minLength={8}
101
- />
102
- </div>
103
- <div className="form-group">
104
- <label htmlFor="register-confirm-password">Confirm Password</label>
105
- <input
106
- id="register-confirm-password"
107
- type="password"
108
- value={confirmPassword}
109
- onChange={(e) => setConfirmPassword(e.target.value)}
110
- required
111
- placeholder="Confirm your password"
112
- autoComplete="new-password"
113
- />
114
- </div>
115
- <button
116
- type="submit"
117
- className={styles.authSubmit}
118
- disabled={loading}
119
- >
120
- {loading ? 'Creating account...' : 'Create Account'}
121
- </button>
122
- </form>
123
-
124
- <p className={styles.authFooter}>
125
- Already have an account?{' '}
126
- <Link href="/account/login">Sign in</Link>
127
- </p>
128
- </div>
129
- </div>
130
- )
131
- }
@@ -1,116 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useCallback, type FormEvent } from 'react'
4
- import styles from '@/styles/pages/pdp.module.css'
5
- import { getNymbalClient } from '@/lib/client'
6
-
7
- interface ReviewFormProps {
8
- productId: string
9
- }
10
-
11
- export function ReviewForm({ productId }: ReviewFormProps) {
12
- const [rating, setRating] = useState(5)
13
- const [title, setTitle] = useState('')
14
- const [body, setBody] = useState('')
15
- const [submitting, setSubmitting] = useState(false)
16
- const [success, setSuccess] = useState(false)
17
- const [error, setError] = useState<string | null>(null)
18
-
19
- const handleSubmit = useCallback(
20
- async (e: FormEvent) => {
21
- e.preventDefault()
22
- setSubmitting(true)
23
- setError(null)
24
-
25
- try {
26
- await getNymbalClient().adapter.reviews.submit({ productId, rating, title, body })
27
- setSuccess(true)
28
- setTitle('')
29
- setBody('')
30
- setRating(5)
31
- } catch {
32
- setError('Failed to submit review.')
33
- } finally {
34
- setSubmitting(false)
35
- }
36
- },
37
- [productId, rating, title, body],
38
- )
39
-
40
- if (success) {
41
- return (
42
- <div className={styles.reviewsSection}>
43
- <h3 className={styles.reviewsTitle}>Write a Review</h3>
44
- <p style={{ fontSize: 'var(--nymbal-font-size-sm)', color: 'var(--nymbal-color-success)' }}>
45
- Thank you for your review! It has been submitted successfully.
46
- </p>
47
- </div>
48
- )
49
- }
50
-
51
- return (
52
- <div className={styles.reviewsSection}>
53
- <h3 className={styles.reviewsTitle}>Write a Review</h3>
54
-
55
- {error && (
56
- <p
57
- style={{
58
- fontSize: 'var(--nymbal-font-size-sm)',
59
- color: 'var(--nymbal-color-error)',
60
- marginBottom: 'var(--nymbal-spacing-md)',
61
- }}
62
- >
63
- {error}
64
- </p>
65
- )}
66
-
67
- <form onSubmit={handleSubmit} data-testid="review-form">
68
- <div className="form-group">
69
- <label htmlFor="review-rating">Rating</label>
70
- <div className={styles.reviewStars} style={{ cursor: 'pointer', fontSize: '1.5rem' }}>
71
- {Array.from({ length: 5 }, (_, i) => (
72
- <span
73
- key={i}
74
- onClick={() => setRating(i + 1)}
75
- className={i < rating ? styles.starFilled : styles.starEmpty}
76
- role="button"
77
- tabIndex={0}
78
- aria-label={`Rate ${i + 1} star${i === 0 ? '' : 's'}`}
79
- onKeyDown={(e) => {
80
- if (e.key === 'Enter' || e.key === ' ') setRating(i + 1)
81
- }}
82
- >
83
- ★
84
- </span>
85
- ))}
86
- </div>
87
- </div>
88
- <div className="form-group">
89
- <label htmlFor="review-title">Title</label>
90
- <input
91
- id="review-title"
92
- type="text"
93
- value={title}
94
- onChange={(e) => setTitle(e.target.value)}
95
- required
96
- placeholder="Summarise your experience"
97
- />
98
- </div>
99
- <div className="form-group">
100
- <label htmlFor="review-body">Review</label>
101
- <textarea
102
- id="review-body"
103
- value={body}
104
- onChange={(e) => setBody(e.target.value)}
105
- required
106
- rows={4}
107
- placeholder="Tell us what you thought..."
108
- />
109
- </div>
110
- <button type="submit" className="btn btn-primary" disabled={submitting}>
111
- {submitting ? 'Submitting...' : 'Submit Review'}
112
- </button>
113
- </form>
114
- </div>
115
- )
116
- }
@@ -1,27 +0,0 @@
1
- 'use client'
2
-
3
- import { useCallback } from 'react'
4
- import { useRouter } from 'next/navigation'
5
- import { SearchBar } from '@nymbalhq/react'
6
-
7
- interface SearchProduct {
8
- slug: string
9
- }
10
-
11
- export function SearchBarWrapper() {
12
- const router = useRouter()
13
-
14
- const handleSelect = useCallback(
15
- (event: CustomEvent<{ product: unknown }>) => {
16
- const product = event.detail.product as SearchProduct
17
- if (product?.slug) {
18
- router.push(`/product/${encodeURIComponent(product.slug)}`)
19
- }
20
- },
21
- [router],
22
- )
23
-
24
- return (
25
- <SearchBar placeholder="Search products..." debounceMs={300} onSearchSelect={handleSelect} />
26
- )
27
- }