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,181 +0,0 @@
1
- 'use client'
2
-
3
- import { useCallback, useEffect } from 'react'
4
- import { useSearchParams } from 'next/navigation'
5
- import Link from 'next/link'
6
- import Image from 'next/image'
7
- import { ProductFilter, useSearch } from '@nymbalhq/react'
8
- import { formatPrice } from '@/lib/format'
9
- import styles from '@/styles/pages/search.module.css'
10
-
11
- export function SearchResults() {
12
- const searchParams = useSearchParams()
13
- const query = searchParams.get('q') ?? ''
14
- const category = searchParams.get('category')
15
- const {
16
- results,
17
- total,
18
- nextCursor,
19
- facets,
20
- loading,
21
- query: searched,
22
- category: searchedCategory,
23
- search,
24
- loadMore,
25
- } = useSearch()
26
-
27
- useEffect(() => {
28
- // Runs when the URL changes; the filter has already searched the category it writes there.
29
- if (query && (query !== searched || category !== searchedCategory)) {
30
- void search(query, { category })
31
- }
32
- }, [query, category, search])
33
-
34
- const onFilterChange = useCallback((event: CustomEvent<{ name: string; value: string }>) => {
35
- const url = new URL(window.location.href)
36
- if (event.detail.value) url.searchParams.set('category', event.detail.value)
37
- else url.searchParams.delete('category')
38
- window.history.replaceState(null, '', url.toString())
39
- }, [])
40
-
41
- return (
42
- <div className={`container ${styles.page}`}>
43
- <div className={styles.header}>
44
- <h1 className={styles.pageTitle}>Search Results</h1>
45
- {query && (
46
- <p className={styles.queryText}>
47
- Showing results for <span className={styles.queryTerm}>&ldquo;{query}&rdquo;</span>
48
- </p>
49
- )}
50
- </div>
51
-
52
- {loading && results.length === 0 ? (
53
- <div className={styles.loading}>
54
- <div className="spinner spinner-lg" role="status">
55
- <span className="visually-hidden">Searching...</span>
56
- </div>
57
- </div>
58
- ) : !query ? (
59
- <div className={styles.empty}>
60
- <div className={styles.emptyIcon}>
61
- <svg
62
- viewBox="0 0 24 24"
63
- fill="none"
64
- stroke="currentColor"
65
- strokeWidth="2"
66
- strokeLinecap="round"
67
- strokeLinejoin="round"
68
- aria-hidden="true"
69
- >
70
- <circle cx="11" cy="11" r="8" />
71
- <line x1="21" y1="21" x2="16.65" y2="16.65" />
72
- </svg>
73
- </div>
74
- <h2 className={styles.emptyTitle}>Search our store</h2>
75
- <p className={styles.emptyText}>
76
- Use the search bar above to find what you are looking for.
77
- </p>
78
- </div>
79
- ) : results.length > 0 ? (
80
- <div className={facets.length > 0 ? styles.layout : undefined}>
81
- {facets.length > 0 && (
82
- <aside className={styles.sidebar}>
83
- <ProductFilter source="search" onFilterChange={onFilterChange} />
84
- </aside>
85
- )}
86
- <div>
87
- <p className={styles.resultCount} data-testid="search-total">
88
- {total} result{total !== 1 ? 's' : ''} found
89
- </p>
90
- <div className={styles.grid}>
91
- {results.map((product) => {
92
- const media = Array.isArray(product.media) ? product.media : []
93
- const imageUrl =
94
- media.length > 0 && typeof (media[0] as Record<string, unknown>)?.url === 'string'
95
- ? (media[0] as Record<string, string>).url
96
- : null
97
- return (
98
- <Link
99
- key={product.id}
100
- href={`/product/${product.slug}`}
101
- className={styles.productCard}
102
- data-testid={`product-card-${product.slug}`}
103
- >
104
- <div className={styles.productImage}>
105
- {imageUrl && (
106
- <Image
107
- src={imageUrl}
108
- alt={product.name}
109
- width={400}
110
- height={400}
111
- sizes="(max-width: 639px) 100vw, (max-width: 1023px) 50vw, 25vw"
112
- />
113
- )}
114
- </div>
115
- <div className={styles.productInfo}>
116
- <p className={styles.productName}>{product.name}</p>
117
- {product.priceMinor != null && product.currency && (
118
- <p className={styles.productPrice}>
119
- {formatPrice(product.priceMinor, product.currency)}
120
- </p>
121
- )}
122
- </div>
123
- </Link>
124
- )
125
- })}
126
- </div>
127
- {nextCursor !== null && (
128
- <div className={styles.pagination}>
129
- <button
130
- type="button"
131
- className="btn btn-secondary"
132
- onClick={() => void loadMore()}
133
- disabled={loading}
134
- data-testid="search-load-more"
135
- >
136
- {loading ? 'Loading...' : 'Load more'}
137
- </button>
138
- </div>
139
- )}
140
- </div>
141
- </div>
142
- ) : (
143
- <div className={styles.empty}>
144
- <div className={styles.emptyIcon}>
145
- <svg
146
- viewBox="0 0 24 24"
147
- fill="none"
148
- stroke="currentColor"
149
- strokeWidth="2"
150
- strokeLinecap="round"
151
- strokeLinejoin="round"
152
- aria-hidden="true"
153
- >
154
- <circle cx="11" cy="11" r="8" />
155
- <line x1="21" y1="21" x2="16.65" y2="16.65" />
156
- </svg>
157
- </div>
158
- <h2 className={styles.emptyTitle}>No results found</h2>
159
- <p className={styles.emptyText}>
160
- We could not find anything matching &ldquo;{query}&rdquo;. Try a different search term.
161
- </p>
162
- </div>
163
- )}
164
-
165
- <div className={styles.suggestions}>
166
- <h3 className={styles.suggestionsTitle}>Popular searches</h3>
167
- <div className={styles.suggestionTags}>
168
- {['Shirts', 'Shoes', 'Jackets', 'Accessories', 'New Arrivals'].map((term) => (
169
- <Link
170
- key={term}
171
- href={`/search?q=${encodeURIComponent(term)}`}
172
- className={styles.suggestionTag}
173
- >
174
- {term}
175
- </Link>
176
- ))}
177
- </div>
178
- </div>
179
- </div>
180
- )
181
- }
@@ -1,36 +0,0 @@
1
- import Link from 'next/link'
2
- import styles from '@/styles/pages/pdp.module.css'
3
-
4
- interface BreadcrumbItem {
5
- label: string
6
- href?: string
7
- }
8
-
9
- interface BreadcrumbProps {
10
- items: BreadcrumbItem[]
11
- }
12
-
13
- export function Breadcrumb({ items }: BreadcrumbProps) {
14
- return (
15
- <nav aria-label="Breadcrumb" data-testid="breadcrumb">
16
- <ol className={styles.breadcrumb}>
17
- {items.map((item, index) => (
18
- <li key={index} style={{ display: 'inline-flex', alignItems: 'center', gap: 'inherit' }}>
19
- {index > 0 && (
20
- <span className={styles.breadcrumbSeparator} aria-hidden="true">
21
- /
22
- </span>
23
- )}
24
- {item.href ? (
25
- <Link href={item.href} className={styles.breadcrumbLink}>
26
- {item.label}
27
- </Link>
28
- ) : (
29
- <span aria-current="page">{item.label}</span>
30
- )}
31
- </li>
32
- ))}
33
- </ol>
34
- </nav>
35
- )
36
- }
@@ -1,29 +0,0 @@
1
- import Link from 'next/link'
2
- import type { Category } from '@nymbalhq/types'
3
- import styles from '@/styles/pages/home.module.css'
4
-
5
- interface CategoryNavProps {
6
- categories: Category[]
7
- }
8
-
9
- export function CategoryNav({ categories }: CategoryNavProps) {
10
- if (categories.length === 0) return null
11
-
12
- return (
13
- <section className={`container ${styles.categoriesSection}`}>
14
- <h2 className="section-title">Shop by Category</h2>
15
- <div className={styles.categoriesGrid}>
16
- {categories.map((category) => (
17
- <Link
18
- key={category.id}
19
- href={`/category/${category.slug}`}
20
- className={styles.categoryCard}
21
- data-testid={`category-${category.slug}`}
22
- >
23
- {category.name}
24
- </Link>
25
- ))}
26
- </div>
27
- </section>
28
- )
29
- }
@@ -1,60 +0,0 @@
1
- import styles from '@/styles/pages/checkout.module.css'
2
-
3
- const STEPS = [
4
- { label: 'Contact' },
5
- { label: 'Shipping' },
6
- { label: 'Payment' },
7
- ]
8
-
9
- interface CheckoutStepsProps {
10
- currentStep: number
11
- }
12
-
13
- export function CheckoutSteps({ currentStep }: CheckoutStepsProps) {
14
- return (
15
- <div className={styles.steps} data-testid="checkout-steps">
16
- {STEPS.map((step, index) => {
17
- const isCompleted = index < currentStep
18
- const isActive = index === currentStep
19
-
20
- return (
21
- <div key={step.label} style={{ display: 'contents' }}>
22
- {index > 0 && (
23
- <div
24
- className={`${styles.stepConnector} ${
25
- isCompleted ? styles.stepConnectorCompleted : ''
26
- }`}
27
- />
28
- )}
29
- <div
30
- className={`${styles.step} ${isActive ? styles.stepActive : ''} ${
31
- isCompleted ? styles.stepCompleted : ''
32
- }`}
33
- >
34
- <span className={styles.stepNumber}>
35
- {isCompleted ? (
36
- <svg
37
- width="14"
38
- height="14"
39
- viewBox="0 0 24 24"
40
- fill="none"
41
- stroke="currentColor"
42
- strokeWidth="3"
43
- strokeLinecap="round"
44
- strokeLinejoin="round"
45
- aria-hidden="true"
46
- >
47
- <polyline points="20 6 9 17 4 12" />
48
- </svg>
49
- ) : (
50
- index + 1
51
- )}
52
- </span>
53
- <span className={styles.stepLabel}>{step.label}</span>
54
- </div>
55
- </div>
56
- )
57
- })}
58
- </div>
59
- )
60
- }
@@ -1,64 +0,0 @@
1
- .footer {
2
- background-color: #fafafa;
3
- border-top: 1px solid var(--nymbal-color-border);
4
- margin-top: var(--nymbal-spacing-xl);
5
- }
6
-
7
- .inner {
8
- padding-block: var(--nymbal-spacing-xl);
9
- }
10
-
11
- .columns {
12
- display: grid;
13
- grid-template-columns: 1fr;
14
- gap: var(--nymbal-spacing-xl);
15
- }
16
-
17
- @media (min-width: 640px) {
18
- .columns {
19
- grid-template-columns: repeat(3, 1fr);
20
- }
21
- }
22
-
23
- .column {
24
- min-width: 0;
25
- }
26
-
27
- .heading {
28
- font-size: var(--nymbal-font-size-sm);
29
- font-weight: var(--nymbal-font-weight-bold);
30
- text-transform: uppercase;
31
- letter-spacing: 0.05em;
32
- color: var(--nymbal-color-text);
33
- margin-bottom: var(--nymbal-spacing-md);
34
- }
35
-
36
- .list {
37
- list-style: none;
38
- display: flex;
39
- flex-direction: column;
40
- gap: var(--nymbal-spacing-sm);
41
- }
42
-
43
- .link {
44
- font-size: var(--nymbal-font-size-sm);
45
- color: var(--nymbal-color-text-muted);
46
- text-decoration: none;
47
- transition: color var(--nymbal-transition-fast);
48
- }
49
-
50
- .link:hover {
51
- color: var(--nymbal-color-text);
52
- text-decoration: none;
53
- }
54
-
55
- .bottom {
56
- margin-top: var(--nymbal-spacing-xl);
57
- padding-top: var(--nymbal-spacing-lg);
58
- border-top: 1px solid var(--nymbal-color-border);
59
- }
60
-
61
- .copyright {
62
- font-size: var(--nymbal-font-size-xs);
63
- color: var(--nymbal-color-text-muted);
64
- }
@@ -1,79 +0,0 @@
1
- import Link from 'next/link'
2
- import styles from './Footer.module.css'
3
-
4
- const SHOP_LINKS = [
5
- { href: '/products', label: 'All Products' },
6
- { href: '/products?category=new', label: 'New Arrivals' },
7
- { href: '/products?category=sale', label: 'Sale' },
8
- ]
9
-
10
- const ACCOUNT_LINKS = [
11
- { href: '/account', label: 'My Account' },
12
- { href: '/account/orders', label: 'Order History' },
13
- { href: '/account/login', label: 'Sign In' },
14
- { href: '/account/register', label: 'Create Account' },
15
- ]
16
-
17
- const ABOUT_LINKS = [
18
- { href: '/about', label: 'About Us' },
19
- { href: '/contact', label: 'Contact' },
20
- { href: '/shipping', label: 'Shipping & Returns' },
21
- { href: '/privacy', label: 'Privacy Policy' },
22
- ]
23
-
24
- export function Footer() {
25
- const year = new Date().getFullYear()
26
-
27
- return (
28
- <footer className={styles.footer} data-testid="site-footer">
29
- <div className={`container ${styles.inner}`}>
30
- <div className={styles.columns}>
31
- <div className={styles.column}>
32
- <h2 className={styles.heading}>Shop</h2>
33
- <ul className={styles.list}>
34
- {SHOP_LINKS.map((link) => (
35
- <li key={link.href}>
36
- <Link href={link.href} className={styles.link}>
37
- {link.label}
38
- </Link>
39
- </li>
40
- ))}
41
- </ul>
42
- </div>
43
-
44
- <div className={styles.column}>
45
- <h2 className={styles.heading}>Account</h2>
46
- <ul className={styles.list}>
47
- {ACCOUNT_LINKS.map((link) => (
48
- <li key={link.href}>
49
- <Link href={link.href} className={styles.link}>
50
- {link.label}
51
- </Link>
52
- </li>
53
- ))}
54
- </ul>
55
- </div>
56
-
57
- <div className={styles.column}>
58
- <h2 className={styles.heading}>About</h2>
59
- <ul className={styles.list}>
60
- {ABOUT_LINKS.map((link) => (
61
- <li key={link.href}>
62
- <Link href={link.href} className={styles.link}>
63
- {link.label}
64
- </Link>
65
- </li>
66
- ))}
67
- </ul>
68
- </div>
69
- </div>
70
-
71
- <div className={styles.bottom}>
72
- <p className={styles.copyright}>
73
- &copy; {year} Nymbal. All rights reserved.
74
- </p>
75
- </div>
76
- </div>
77
- </footer>
78
- )
79
- }
@@ -1,147 +0,0 @@
1
- .header {
2
- position: sticky;
3
- top: 0;
4
- z-index: var(--nymbal-z-dropdown);
5
- background-color: var(--nymbal-color-surface);
6
- border-bottom: 1px solid var(--nymbal-color-border);
7
- }
8
-
9
- .inner {
10
- display: flex;
11
- align-items: center;
12
- gap: var(--nymbal-spacing-lg);
13
- height: 4rem;
14
- position: relative;
15
- }
16
-
17
- .logo {
18
- font-size: var(--nymbal-font-size-xl);
19
- font-weight: var(--nymbal-font-weight-bold);
20
- color: var(--nymbal-color-primary);
21
- text-decoration: none;
22
- letter-spacing: -0.02em;
23
- flex-shrink: 0;
24
- }
25
-
26
- .logo:hover {
27
- color: var(--nymbal-color-primary);
28
- text-decoration: none;
29
- }
30
-
31
- .nav {
32
- display: none;
33
- }
34
-
35
- .navList {
36
- display: flex;
37
- gap: var(--nymbal-spacing-lg);
38
- list-style: none;
39
- }
40
-
41
- .navLink {
42
- font-size: var(--nymbal-font-size-sm);
43
- font-weight: var(--nymbal-font-weight-medium);
44
- color: var(--nymbal-color-text);
45
- text-decoration: none;
46
- transition: color var(--nymbal-transition-fast);
47
- }
48
-
49
- .navLink:hover {
50
- color: var(--nymbal-color-accent);
51
- text-decoration: none;
52
- }
53
-
54
- .actions {
55
- display: flex;
56
- align-items: center;
57
- gap: var(--nymbal-spacing-md);
58
- margin-left: auto;
59
- }
60
-
61
- .search {
62
- display: none;
63
- }
64
-
65
- .iconLink {
66
- display: flex;
67
- align-items: center;
68
- justify-content: center;
69
- color: var(--nymbal-color-text);
70
- transition: color var(--nymbal-transition-fast);
71
- }
72
-
73
- .iconLink:hover {
74
- color: var(--nymbal-color-accent);
75
- }
76
-
77
- /* ── Mobile menu (CSS-only) ───────────────────────────────────────── */
78
-
79
- .menuCheckbox {
80
- display: none;
81
- }
82
-
83
- .menuButton {
84
- display: flex;
85
- align-items: center;
86
- justify-content: center;
87
- cursor: pointer;
88
- color: var(--nymbal-color-text);
89
- order: 10;
90
- }
91
-
92
- .mobileNav {
93
- display: none;
94
- position: absolute;
95
- top: 100%;
96
- left: 0;
97
- right: 0;
98
- background: var(--nymbal-color-surface);
99
- border-bottom: 1px solid var(--nymbal-color-border);
100
- box-shadow: var(--nymbal-shadow-md);
101
- z-index: var(--nymbal-z-dropdown);
102
- }
103
-
104
- .menuCheckbox:checked ~ .mobileNav {
105
- display: block;
106
- }
107
-
108
- .mobileNavList {
109
- list-style: none;
110
- padding: var(--nymbal-spacing-md);
111
- }
112
-
113
- .mobileNavLink {
114
- display: block;
115
- padding: var(--nymbal-spacing-sm) 0;
116
- font-size: var(--nymbal-font-size-base);
117
- font-weight: var(--nymbal-font-weight-medium);
118
- color: var(--nymbal-color-text);
119
- text-decoration: none;
120
- border-bottom: 1px solid var(--nymbal-color-border);
121
- }
122
-
123
- .mobileNavLink:hover {
124
- color: var(--nymbal-color-accent);
125
- text-decoration: none;
126
- }
127
-
128
- /* ── Desktop ──────────────────────────────────────────────────────── */
129
-
130
- @media (min-width: 1024px) {
131
- .nav {
132
- display: block;
133
- }
134
-
135
- .search {
136
- display: block;
137
- width: 16rem;
138
- }
139
-
140
- .menuButton {
141
- display: none;
142
- }
143
-
144
- .mobileNav {
145
- display: none !important;
146
- }
147
- }
@@ -1,79 +0,0 @@
1
- import { formatPrice } from '@/lib/format'
2
- import styles from '@/styles/pages/confirmation.module.css'
3
-
4
- interface OrderLineItem {
5
- variantId: string
6
- productId: string
7
- productName: string
8
- variantName: string
9
- sku: string
10
- qty: number
11
- unitPriceMinor: number
12
- lineTotalMinor: number
13
- imageUrl?: string
14
- }
15
-
16
- interface OrderSummaryProps {
17
- items: OrderLineItem[]
18
- subtotalMinor: number
19
- currency: string
20
- shippingTotalMinor: number
21
- taxTotalMinor: number
22
- totalMinor: number
23
- }
24
-
25
- export function OrderSummary({
26
- items,
27
- subtotalMinor,
28
- currency,
29
- shippingTotalMinor,
30
- taxTotalMinor,
31
- totalMinor,
32
- }: OrderSummaryProps) {
33
- return (
34
- <div className={styles.orderCard}>
35
- <h3 className={styles.orderCardTitle}>Order Items</h3>
36
- <div className={styles.lineItems}>
37
- {items.map((item) => (
38
- <div key={item.variantId} className={styles.lineItem}>
39
- {item.imageUrl && (
40
- <div className={styles.lineItemImage}>
41
- <img src={item.imageUrl} alt={item.productName} />
42
- </div>
43
- )}
44
- <div className={styles.lineItemInfo}>
45
- <p className={styles.lineItemName}>{item.productName}</p>
46
- {item.variantName && (
47
- <p className={styles.lineItemVariant}>{item.variantName}</p>
48
- )}
49
- <p className={styles.lineItemQty}>Qty: {item.qty}</p>
50
- </div>
51
- <span className={styles.lineItemPrice}>
52
- {formatPrice(item.lineTotalMinor, currency)}
53
- </span>
54
- </div>
55
- ))}
56
- </div>
57
- <div className={styles.totals}>
58
- <div className={styles.totalRow}>
59
- <span className={styles.totalLabel}>Subtotal</span>
60
- <span>{formatPrice(subtotalMinor, currency)}</span>
61
- </div>
62
- <div className={styles.totalRow}>
63
- <span className={styles.totalLabel}>Shipping</span>
64
- <span>
65
- {shippingTotalMinor === 0 ? 'Free' : formatPrice(shippingTotalMinor, currency)}
66
- </span>
67
- </div>
68
- <div className={styles.totalRow}>
69
- <span className={styles.totalLabel}>Tax</span>
70
- <span>{formatPrice(taxTotalMinor, currency)}</span>
71
- </div>
72
- <div className={`${styles.totalRow} ${styles.totalGrand}`}>
73
- <span>Total</span>
74
- <span>{formatPrice(totalMinor, currency)}</span>
75
- </div>
76
- </div>
77
- </div>
78
- )
79
- }