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.
Files changed (167) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +14 -12
  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 -27
  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 +126 -20
  13. package/scaffold/templates/astro/src/lib/chrome.ts +59 -0
  14. package/scaffold/templates/astro/src/lib/client.ts +4 -2
  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 +41 -26
  24. package/scaffold/templates/astro/src/pages/checkout.astro +34 -90
  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 +46 -29
  32. package/scaffold/templates/astro/src/pages/products/index.astro +40 -26
  33. package/scaffold/templates/astro/src/pages/search.astro +13 -8
  34. package/scaffold/templates/astro/src/scripts/init-client.ts +14 -0
  35. package/scaffold/templates/astro/src/scripts/pdp.ts +47 -53
  36. package/scaffold/templates/astro/src/scripts/plp.ts +21 -98
  37. package/scaffold/templates/astro/src/scripts/search.ts +19 -120
  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 -167
  44. package/scaffold/templates/astro/src/styles/pages/plp.css +3 -42
  45. package/scaffold/templates/astro/src/styles/pages/search.css +13 -100
  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 +64 -28
  82. package/scaffold/templates/nextjs/components/client/elements.tsx +15 -0
  83. package/scaffold/templates/nextjs/components/server/Header.tsx +33 -97
  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 +162 -46
  88. package/scaffold/templates/nextjs/lib/chrome.ts +40 -0
  89. package/scaffold/templates/nextjs/lib/client.ts +3 -0
  90. package/scaffold/templates/nextjs/lib/fragments.ts +16 -0
  91. package/scaffold/templates/nextjs/lib/header.ts +39 -0
  92. package/scaffold/templates/nextjs/lib/render.ts +14 -0
  93. package/scaffold/templates/nextjs/next.config.mjs +32 -0
  94. package/scaffold/templates/nextjs/package.json +28 -29
  95. package/scaffold/templates/nextjs/styles/pages/account.module.css +0 -70
  96. package/scaffold/templates/nextjs/styles/pages/confirmation.module.css +6 -85
  97. package/scaffold/templates/nextjs/styles/pages/home.module.css +2 -103
  98. package/scaffold/templates/nextjs/styles/pages/pdp.module.css +3 -179
  99. package/scaffold/templates/nextjs/styles/pages/plp.module.css +8 -141
  100. package/scaffold/templates/nextjs/styles/pages/search.module.css +8 -128
  101. package/scaffold/templates/nextjs/tsconfig.dist.json +5 -0
  102. package/scaffold/templates/astro/src/components/AddressForm.astro +0 -107
  103. package/scaffold/templates/astro/src/components/Breadcrumb.astro +0 -20
  104. package/scaffold/templates/astro/src/components/CategoryNav.astro +0 -27
  105. package/scaffold/templates/astro/src/components/CheckoutSteps.astro +0 -29
  106. package/scaffold/templates/astro/src/components/Footer.astro +0 -108
  107. package/scaffold/templates/astro/src/components/Header.astro +0 -192
  108. package/scaffold/templates/astro/src/components/ImageGallery.astro +0 -42
  109. package/scaffold/templates/astro/src/components/OrderSummary.astro +0 -53
  110. package/scaffold/templates/astro/src/components/Pagination.astro +0 -13
  111. package/scaffold/templates/astro/src/components/PriceDisplay.astro +0 -25
  112. package/scaffold/templates/astro/src/components/ProductCard.astro +0 -44
  113. package/scaffold/templates/astro/src/components/RelatedProducts.astro +0 -28
  114. package/scaffold/templates/astro/src/components/ReviewForm.astro +0 -74
  115. package/scaffold/templates/astro/src/components/ReviewList.astro +0 -41
  116. package/scaffold/templates/astro/src/components/SortDropdown.astro +0 -26
  117. package/scaffold/templates/astro/src/components/StockBadge.astro +0 -32
  118. package/scaffold/templates/astro/src/scripts/auth-forms.ts +0 -82
  119. package/scaffold/templates/astro/src/scripts/cart-page.ts +0 -143
  120. package/scaffold/templates/astro/src/scripts/checkout.ts +0 -272
  121. package/scaffold/templates/astro/src/scripts/image-gallery.ts +0 -22
  122. package/scaffold/templates/astro/src/scripts/profile-form.ts +0 -57
  123. package/scaffold/templates/astro/src/scripts/review-form.ts +0 -27
  124. package/scaffold/templates/astro/src/styles/pages/cart.css +0 -243
  125. package/scaffold/templates/astro/src/styles/pages/checkout.css +0 -270
  126. package/scaffold/templates/nextjs/app/account/addresses/page.tsx +0 -10
  127. package/scaffold/templates/nextjs/app/account/login/page.tsx +0 -23
  128. package/scaffold/templates/nextjs/app/account/profile/page.tsx +0 -10
  129. package/scaffold/templates/nextjs/app/account/register/page.tsx +0 -10
  130. package/scaffold/templates/nextjs/app/cart/page.tsx +0 -10
  131. package/scaffold/templates/nextjs/app/category/[slug]/page.tsx +0 -58
  132. package/scaffold/templates/nextjs/app/checkout/page.tsx +0 -10
  133. package/scaffold/templates/nextjs/app/page.tsx +0 -63
  134. package/scaffold/templates/nextjs/app/product/[slug]/page.tsx +0 -111
  135. package/scaffold/templates/nextjs/app/products/page.tsx +0 -40
  136. package/scaffold/templates/nextjs/app/search/page.tsx +0 -24
  137. package/scaffold/templates/nextjs/components/client/AddressManager.tsx +0 -181
  138. package/scaffold/templates/nextjs/components/client/CartPage.tsx +0 -120
  139. package/scaffold/templates/nextjs/components/client/CheckoutForm.tsx +0 -523
  140. package/scaffold/templates/nextjs/components/client/ImageGallery.tsx +0 -68
  141. package/scaffold/templates/nextjs/components/client/LoginForm.tsx +0 -89
  142. package/scaffold/templates/nextjs/components/client/MiniCartWrapper.tsx +0 -12
  143. package/scaffold/templates/nextjs/components/client/ProductGrid.tsx +0 -148
  144. package/scaffold/templates/nextjs/components/client/ProfileForm.tsx +0 -112
  145. package/scaffold/templates/nextjs/components/client/RegisterForm.tsx +0 -131
  146. package/scaffold/templates/nextjs/components/client/ReviewForm.tsx +0 -116
  147. package/scaffold/templates/nextjs/components/client/SearchBarWrapper.tsx +0 -27
  148. package/scaffold/templates/nextjs/components/client/SearchResults.tsx +0 -141
  149. package/scaffold/templates/nextjs/components/client/SortDropdown.tsx +0 -35
  150. package/scaffold/templates/nextjs/components/server/Breadcrumb.tsx +0 -36
  151. package/scaffold/templates/nextjs/components/server/CategoryNav.tsx +0 -29
  152. package/scaffold/templates/nextjs/components/server/CheckoutSteps.tsx +0 -60
  153. package/scaffold/templates/nextjs/components/server/Footer.module.css +0 -64
  154. package/scaffold/templates/nextjs/components/server/Footer.tsx +0 -79
  155. package/scaffold/templates/nextjs/components/server/Header.module.css +0 -147
  156. package/scaffold/templates/nextjs/components/server/OrderSummary.tsx +0 -79
  157. package/scaffold/templates/nextjs/components/server/PriceDisplay.tsx +0 -31
  158. package/scaffold/templates/nextjs/components/server/ProductCard.tsx +0 -59
  159. package/scaffold/templates/nextjs/components/server/RelatedProducts.tsx +0 -31
  160. package/scaffold/templates/nextjs/components/server/ReviewList.tsx +0 -62
  161. package/scaffold/templates/nextjs/components/server/StockBadge.tsx +0 -19
  162. package/scaffold/templates/nextjs/styles/pages/cart.module.css +0 -240
  163. package/scaffold/templates/nextjs/styles/pages/checkout.module.css +0 -404
  164. /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/layout.tsx +0 -0
  165. /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/orders/[orderNumber]/page.tsx +0 -0
  166. /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/orders/page.tsx +0 -0
  167. /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/page.tsx +0 -0
@@ -1,35 +0,0 @@
1
- 'use client'
2
-
3
- import type { ProductSortKey } from '@nymbalhq/types'
4
- import styles from '@/styles/pages/plp.module.css'
5
-
6
- const OPTIONS: Array<{ value: ProductSortKey; label: string }> = [
7
- { value: 'newest', label: 'Newest' },
8
- { value: 'price-asc', label: 'Price: Low to High' },
9
- { value: 'price-desc', label: 'Price: High to Low' },
10
- { value: 'name-asc', label: 'Name: A to Z' },
11
- { value: 'name-desc', label: 'Name: Z to A' },
12
- ]
13
-
14
- interface SortDropdownProps {
15
- value: ProductSortKey
16
- onChange: (sort: ProductSortKey) => void
17
- }
18
-
19
- export function SortDropdown({ value, onChange }: SortDropdownProps) {
20
- return (
21
- <select
22
- className={styles.sortSelect}
23
- value={value}
24
- onChange={(e) => onChange(e.target.value as ProductSortKey)}
25
- data-testid="sort-dropdown"
26
- aria-label="Sort products"
27
- >
28
- {OPTIONS.map((option) => (
29
- <option key={option.value} value={option.value}>
30
- {option.label}
31
- </option>
32
- ))}
33
- </select>
34
- )
35
- }
@@ -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
- }
@@ -1,31 +0,0 @@
1
- import { formatPrice } from '@/lib/format'
2
- import styles from '@/styles/pages/pdp.module.css'
3
-
4
- interface PriceDisplayProps {
5
- priceMinor: number
6
- currency: string
7
- compareAtMinor?: number
8
- }
9
-
10
- export function PriceDisplay({ priceMinor, currency, compareAtMinor }: PriceDisplayProps) {
11
- const hasDiscount = compareAtMinor != null && compareAtMinor > priceMinor
12
-
13
- return (
14
- <div className={styles.price} data-testid="product-price">
15
- <span>{formatPrice(priceMinor, currency)}</span>
16
- {hasDiscount && (
17
- <span
18
- style={{
19
- textDecoration: 'line-through',
20
- fontSize: '0.875em',
21
- color: 'var(--nymbal-color-text-muted)',
22
- marginLeft: 'var(--nymbal-spacing-sm)',
23
- fontWeight: 'var(--nymbal-font-weight-normal)',
24
- }}
25
- >
26
- {formatPrice(compareAtMinor, currency)}
27
- </span>
28
- )}
29
- </div>
30
- )
31
- }
@@ -1,59 +0,0 @@
1
- import Link from 'next/link'
2
- import Image from 'next/image'
3
- import { formatPrice } from '@/lib/format'
4
- import styles from '@/styles/pages/home.module.css'
5
-
6
- interface ProductCardProps {
7
- product: {
8
- slug: string
9
- name: string
10
- priceMinor: number | null
11
- priceRange?: { minMinor: number } | null
12
- currency: string | null
13
- media?: unknown
14
- imageUrl?: string
15
- }
16
- }
17
-
18
- function getImageUrl(product: ProductCardProps['product']): string | null {
19
- if (product.imageUrl) return product.imageUrl
20
- if (Array.isArray(product.media) && product.media.length > 0) {
21
- const first = product.media[0] as Record<string, unknown>
22
- if (typeof first?.url === 'string') return first.url
23
- }
24
- return null
25
- }
26
-
27
- export function ProductCard({ product }: ProductCardProps) {
28
- const imageUrl = getImageUrl(product)
29
- // The "from" price, which the price sorts order by.
30
- const fromMinor = product.priceRange?.minMinor ?? product.priceMinor
31
-
32
- return (
33
- <Link
34
- href={`/product/${product.slug}`}
35
- className={styles.productCard}
36
- data-testid={`product-card-${product.slug}`}
37
- >
38
- <div className={styles.productImage}>
39
- {imageUrl ? (
40
- <Image
41
- src={imageUrl}
42
- alt={product.name}
43
- width={400}
44
- height={400}
45
- sizes="(max-width: 639px) 100vw, (max-width: 1023px) 50vw, 25vw"
46
- />
47
- ) : (
48
- <span className="visually-hidden">No image available</span>
49
- )}
50
- </div>
51
- <div className={styles.productInfo}>
52
- <p className={styles.productName}>{product.name}</p>
53
- {fromMinor != null && product.currency && (
54
- <p className={styles.productPrice}>{formatPrice(fromMinor, product.currency)}</p>
55
- )}
56
- </div>
57
- </Link>
58
- )
59
- }
@@ -1,31 +0,0 @@
1
- import { ProductCard } from './ProductCard'
2
- import type { DenormalisedProduct } from '@nymbalhq/sdk'
3
-
4
- interface RelatedProductsProps {
5
- products: DenormalisedProduct[]
6
- }
7
-
8
- export function RelatedProducts({ products }: RelatedProductsProps) {
9
- if (products.length === 0) return null
10
-
11
- return (
12
- <section className="section">
13
- <h2 className="section-title">You may also like</h2>
14
- <div className="product-grid">
15
- {products.map((product) => (
16
- <ProductCard
17
- key={product.id}
18
- product={{
19
- slug: product.slug,
20
- name: product.name,
21
- priceMinor: product.priceMinor ?? null,
22
- priceRange: product.priceRange ?? null,
23
- currency: product.currency ?? null,
24
- media: product.media,
25
- }}
26
- />
27
- ))}
28
- </div>
29
- </section>
30
- )
31
- }