create-magic-storefront 0.5.0 → 0.6.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 (139) hide show
  1. package/package.json +3 -3
  2. package/template/.claude/skills/storefront-design/SKILL.md +14 -10
  3. package/template/AGENTS.md +32 -16
  4. package/template/DESIGN.md +117 -0
  5. package/template/PAGES.md +107 -29
  6. package/template/README.md +15 -3
  7. package/template/app/account/addresses/page.tsx +16 -0
  8. package/template/app/account/layout.tsx +14 -0
  9. package/template/app/account/loading.tsx +15 -0
  10. package/template/app/account/orders/[id]/page.tsx +15 -0
  11. package/template/app/account/page.tsx +12 -244
  12. package/template/app/account/profile/page.tsx +16 -0
  13. package/template/app/blog/(index)/loading.tsx +22 -0
  14. package/template/app/blog/{page.tsx → (index)/page.tsx} +42 -25
  15. package/template/app/blog/[handle]/page.tsx +92 -56
  16. package/template/app/blog/author/[handle]/page.tsx +1 -1
  17. package/template/app/cart/layout.tsx +14 -0
  18. package/template/app/cart/page.tsx +49 -101
  19. package/template/app/checkout/layout.tsx +14 -0
  20. package/template/app/checkout/page.tsx +98 -281
  21. package/template/app/collections/[handle]/layout.tsx +20 -0
  22. package/template/app/collections/[handle]/loading.tsx +23 -0
  23. package/template/app/collections/[handle]/page.tsx +131 -15
  24. package/template/app/collections/page.tsx +49 -0
  25. package/template/app/contacts/page.tsx +196 -0
  26. package/template/app/error.tsx +19 -8
  27. package/template/app/fonts/OFL.txt +93 -0
  28. package/template/app/fonts/index.ts +14 -0
  29. package/template/app/fonts/onest-variable.woff2 +0 -0
  30. package/template/app/globals.css +14 -518
  31. package/template/app/layout.tsx +73 -50
  32. package/template/app/not-found.tsx +34 -4
  33. package/template/app/page.tsx +1 -1
  34. package/template/app/pages/[handle]/page.tsx +1 -1
  35. package/template/app/products/[handle]/layout.tsx +20 -0
  36. package/template/app/products/[handle]/loading.tsx +25 -0
  37. package/template/app/products/[handle]/page.tsx +112 -15
  38. package/template/app/providers.tsx +5 -1
  39. package/template/app/search/loading.tsx +12 -0
  40. package/template/app/search/page.tsx +138 -25
  41. package/template/app/sitemap.ts +5 -1
  42. package/template/app/styles/base.css +138 -0
  43. package/template/app/styles/components.css +1108 -0
  44. package/template/app/styles/layout.css +455 -0
  45. package/template/app/styles/pages.css +1839 -0
  46. package/template/app/styles/reset.css +55 -0
  47. package/template/app/styles/sections.css +415 -0
  48. package/template/app/styles/tokens.css +167 -0
  49. package/template/app/styles/utilities.css +37 -0
  50. package/template/app/wishlist/page.tsx +26 -0
  51. package/template/components/account/account-shell.tsx +100 -0
  52. package/template/components/account/addresses.tsx +247 -0
  53. package/template/components/account/order-detail.tsx +281 -0
  54. package/template/components/account/order-list.tsx +94 -0
  55. package/template/components/account/order-status.tsx +19 -0
  56. package/template/components/account/profile.tsx +129 -0
  57. package/template/components/account/sign-in.tsx +164 -0
  58. package/template/components/article-body.tsx +3 -2
  59. package/template/components/article-card.tsx +37 -12
  60. package/template/components/cart/cart-drawer.tsx +149 -0
  61. package/template/components/cart/cart-lines.tsx +89 -0
  62. package/template/components/cart/cart-summary.tsx +191 -0
  63. package/template/components/catalog/catalog-form.tsx +107 -0
  64. package/template/components/catalog/catalog-layout.tsx +82 -0
  65. package/template/components/catalog/facets-shell.tsx +60 -0
  66. package/template/components/catalog/facets.tsx +216 -0
  67. package/template/components/checkout/order-summary.tsx +84 -0
  68. package/template/components/checkout/radio-card.tsx +34 -0
  69. package/template/components/checkout/step-indicator.tsx +56 -0
  70. package/template/components/checkout/steps.tsx +264 -0
  71. package/template/components/checkout/thank-you.tsx +48 -0
  72. package/template/components/checkout/use-checkout.ts +90 -0
  73. package/template/components/footer/site-footer.tsx +132 -0
  74. package/template/components/header/cart-button.tsx +43 -0
  75. package/template/components/header/desktop-nav.tsx +133 -0
  76. package/template/components/header/mobile-menu.tsx +102 -0
  77. package/template/components/header/site-header.tsx +78 -0
  78. package/template/components/page-loading.tsx +20 -0
  79. package/template/components/pager.tsx +3 -1
  80. package/template/components/product/buy-box.tsx +148 -0
  81. package/template/components/product/gallery.tsx +179 -0
  82. package/template/components/product/pre-order-form.tsx +93 -0
  83. package/template/components/product/product-selection.tsx +31 -0
  84. package/template/components/product/recommendations.tsx +26 -0
  85. package/template/components/product/review-list.tsx +159 -0
  86. package/template/components/product/reviews.tsx +80 -0
  87. package/template/components/product/variant-picker.tsx +75 -0
  88. package/template/components/product-card.tsx +103 -0
  89. package/template/components/product-grid.tsx +27 -57
  90. package/template/components/quick-add.tsx +62 -0
  91. package/template/components/search/predictive-search.tsx +282 -0
  92. package/template/components/sections/announcement-bar.tsx +1 -1
  93. package/template/components/sections/banner.tsx +1 -1
  94. package/template/components/sections/collections.tsx +50 -13
  95. package/template/components/sections/deal-of-day.tsx +2 -2
  96. package/template/components/sections/product-shelves.tsx +13 -5
  97. package/template/components/sections/shoppable-stories.tsx +2 -2
  98. package/template/components/sections/store-reviews.tsx +10 -4
  99. package/template/components/share-button.tsx +42 -0
  100. package/template/components/ui/accordion.tsx +35 -0
  101. package/template/components/ui/badge.tsx +20 -0
  102. package/template/components/ui/breadcrumbs.tsx +31 -0
  103. package/template/components/ui/button.tsx +90 -0
  104. package/template/components/ui/drawer.tsx +84 -0
  105. package/template/components/ui/empty-state.tsx +27 -0
  106. package/template/components/ui/field.tsx +33 -0
  107. package/template/components/ui/icon.tsx +112 -0
  108. package/template/components/ui/price.tsx +35 -0
  109. package/template/components/ui/quantity-stepper.tsx +91 -0
  110. package/template/components/ui/rating.tsx +48 -0
  111. package/template/components/ui/skeleton.tsx +46 -0
  112. package/template/components/wishlist/wishlist-view.tsx +134 -0
  113. package/template/lib/article-outline.ts +35 -0
  114. package/template/lib/catalog-params.ts +170 -0
  115. package/template/lib/contacts.ts +34 -0
  116. package/template/lib/errors.ts +29 -0
  117. package/template/lib/i18n.ts +484 -0
  118. package/template/lib/menu.ts +70 -0
  119. package/template/lib/theme-settings.ts +36 -0
  120. package/template/theme/index.ts +7 -1
  121. package/template/theme/sections/collapsible-content.tsx +75 -0
  122. package/template/theme/sections/collection-list.tsx +32 -4
  123. package/template/theme/sections/featured-collection.tsx +22 -2
  124. package/template/theme/sections/hero.tsx +94 -7
  125. package/template/theme/sections/image-with-text.tsx +19 -3
  126. package/template/theme/sections/multicolumn.tsx +113 -0
  127. package/template/theme/sections/page-content.tsx +10 -4
  128. package/template/theme/sections/product-shelf.tsx +26 -4
  129. package/template/theme/sections/rich-text.tsx +13 -1
  130. package/template/theme/sections/testimonials.tsx +14 -9
  131. package/template/theme/settings.ts +85 -1
  132. package/template/theme/templates/index.json +4 -2
  133. package/template/.screenshots/home-1280.png +0 -0
  134. package/template/.screenshots/home-390.png +0 -0
  135. package/template/.screenshots/home-filled-1280.png +0 -0
  136. package/template/.screenshots/home-filled-390.png +0 -0
  137. package/template/.screenshots/page-1280.png +0 -0
  138. package/template/components/buy-box.tsx +0 -89
  139. package/template/components/cart-link.tsx +0 -15
@@ -1,5 +1,6 @@
1
1
  import { defineBlock, defineSection, type SectionProps } from '@magicstoreai/hydrogen/theme';
2
2
 
3
+ import { Rating } from '@/components/ui/rating';
3
4
  import { locale } from '@/lib/api';
4
5
  import { messages } from '@/lib/i18n';
5
6
 
@@ -65,16 +66,20 @@ export function Testimonials({ settings, blocks }: SectionProps<typeof testimoni
65
66
  return null;
66
67
  }
67
68
  return (
68
- <section className="stack">
69
- <h2>{settings.heading || t.customersSay}</h2>
70
- <div className="testimonials">
69
+ <section>
70
+ <div className="section-header">
71
+ <h2>{settings.heading || t.customersSay}</h2>
72
+ </div>
73
+ <div className="testimonials__grid">
71
74
  {quotes.map(({ id, settings: quote }) => (
72
- <figure key={id} className="stack">
73
- <span className="rating" role="img" aria-label={t.ratedOutOf(quote.rating)}>
74
- {'★'.repeat(quote.rating)}
75
- {'☆'.repeat(5 - quote.rating)}
76
- </span>
77
- <blockquote>{quote.text}</blockquote>
75
+ <figure key={id} className="testimonial">
76
+ <Rating
77
+ average={quote.rating}
78
+ count={1}
79
+ label={t.ratedOutOf(quote.rating)}
80
+ showCount={false}
81
+ />
82
+ <blockquote className="testimonial__text">{quote.text}</blockquote>
78
83
  {quote.author && <figcaption className="muted">{quote.author}</figcaption>}
79
84
  </figure>
80
85
  ))}
@@ -2,7 +2,11 @@ import { defineThemeSettings } from '@magicstoreai/hydrogen/theme';
2
2
 
3
3
  import { label } from './text';
4
4
 
5
- /** Global settings, edited in the admin under the theme's settings. Brand colors stay in `shop.branding`. */
5
+ /**
6
+ * Global settings, edited in the admin under the theme's settings. Brand colors stay in
7
+ * `shop.branding`. `lib/theme-settings.ts` reads them; `app/layout.tsx` turns the visual ones into
8
+ * `data-*` attributes on <html> that `app/styles/tokens.css` reads (DESIGN.md, Theme settings).
9
+ */
6
10
  export const settings = defineThemeSettings([
7
11
  {
8
12
  id: 'announcement',
@@ -16,4 +20,84 @@ export const settings = defineThemeSettings([
16
20
  maxLength: 200,
17
21
  default: '',
18
22
  },
23
+ { type: 'HEADER', content: label('Layout', 'Оформление', 'Bezak') },
24
+ {
25
+ id: 'cornerRadius',
26
+ type: 'SELECT',
27
+ label: label('Corner radius', 'Скругление углов', 'Burchaklar yumaloqligi'),
28
+ info: label(
29
+ 'Buttons, fields, cards and images.',
30
+ 'Кнопки, поля, карточки и изображения.',
31
+ 'Tugmalar, maydonlar, kartochkalar va rasmlar.',
32
+ ),
33
+ default: 'SMALL',
34
+ options: [
35
+ { value: 'NONE', label: label('None', 'Без скругления', "Yo'q") },
36
+ { value: 'SMALL', label: label('Small', 'Небольшое', 'Kichik') },
37
+ { value: 'MEDIUM', label: label('Medium', 'Среднее', "O'rtacha") },
38
+ { value: 'LARGE', label: label('Large', 'Большое', 'Katta') },
39
+ ],
40
+ },
41
+ {
42
+ id: 'stickyHeader',
43
+ type: 'CHECKBOX',
44
+ label: label('Sticky header', 'Закреплённая шапка', 'Qotirilgan sarlavha'),
45
+ default: true,
46
+ },
47
+ { type: 'HEADER', content: label('Product cards', 'Карточки товаров', 'Mahsulot kartochkalari') },
48
+ {
49
+ id: 'productImageRatio',
50
+ type: 'SELECT',
51
+ label: label('Image shape', 'Форма изображения', 'Rasm shakli'),
52
+ default: 'PORTRAIT',
53
+ options: [
54
+ { value: 'PORTRAIT', label: label('Portrait (3:4)', 'Вертикальная (3:4)', 'Tik (3:4)') },
55
+ { value: 'SQUARE', label: label('Square', 'Квадрат', 'Kvadrat') },
56
+ { value: 'NATURAL', label: label('As uploaded', 'Как загружено', 'Yuklanganidek') },
57
+ ],
58
+ },
59
+ {
60
+ id: 'cardStyle',
61
+ type: 'SELECT',
62
+ label: label('Card style', 'Стиль карточки', 'Kartochka uslubi'),
63
+ default: 'MINIMAL',
64
+ options: [
65
+ { value: 'MINIMAL', label: label('Minimal', 'Минимальный', 'Minimal') },
66
+ { value: 'CARD', label: label('With a frame', 'С рамкой', 'Ramkali') },
67
+ ],
68
+ },
69
+ {
70
+ id: 'showVendor',
71
+ type: 'CHECKBOX',
72
+ label: label('Show the brand', 'Показывать бренд', "Brendni ko'rsatish"),
73
+ default: false,
74
+ },
75
+ {
76
+ id: 'showRating',
77
+ type: 'CHECKBOX',
78
+ label: label('Show the rating', 'Показывать рейтинг', "Reytingni ko'rsatish"),
79
+ info: label(
80
+ 'Only for products that have reviews.',
81
+ 'Только у товаров с отзывами.',
82
+ 'Faqat sharhlari bor mahsulotlarda.',
83
+ ),
84
+ default: true,
85
+ },
86
+ { type: 'HEADER', content: label('Cart', 'Корзина', 'Savat') },
87
+ {
88
+ id: 'cartType',
89
+ type: 'SELECT',
90
+ label: label('After adding to cart', 'После добавления в корзину', "Savatga qo'shgandan keyin"),
91
+ default: 'DRAWER',
92
+ options: [
93
+ {
94
+ value: 'DRAWER',
95
+ label: label('Open a side panel', 'Открыть боковую панель', 'Yon panelni ochish'),
96
+ },
97
+ {
98
+ value: 'PAGE',
99
+ label: label('Stay on the page', 'Остаться на странице', 'Sahifada qolish'),
100
+ },
101
+ ],
102
+ },
19
103
  ]);
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "sections": {
3
3
  "hero": { "type": "hero", "settings": {} },
4
+ "collections": { "type": "collection-list", "settings": {} },
4
5
  "featured": { "type": "featured-collection", "settings": {} },
6
+ "story": { "type": "image-with-text", "settings": {} },
5
7
  "new-arrivals": { "type": "product-shelf", "settings": { "source": "NEW_ARRIVALS" } },
6
- "story": { "type": "image-with-text", "settings": {} }
8
+ "testimonials": { "type": "testimonials", "settings": {} }
7
9
  },
8
- "order": ["hero", "featured", "new-arrivals", "story"]
10
+ "order": ["hero", "collections", "featured", "story", "new-arrivals", "testimonials"]
9
11
  }
Binary file
Binary file
Binary file
@@ -1,89 +0,0 @@
1
- 'use client';
2
-
3
- import {
4
- MagicStoreError,
5
- Money,
6
- useAnalytics,
7
- useCart,
8
- useMagicStore,
9
- useVariantSelection,
10
- useWishlist,
11
- } from '@magicstoreai/hydrogen';
12
- import type { Schema } from '@magicstoreai/storefront-client';
13
- import { useEffect, useState } from 'react';
14
- import { messages } from '@/lib/i18n';
15
-
16
- export function BuyBox({ product }: { product: Schema<'ProductDetail'> }) {
17
- const { selectedOptions, selectedVariant, setOption, isAvailable } = useVariantSelection(product);
18
- const { addLine, status } = useCart();
19
- const wishlist = useWishlist();
20
- const analytics = useAnalytics();
21
- const t = messages(useMagicStore().locale);
22
- const [error, setError] = useState<string | null>(null);
23
- const [added, setAdded] = useState(false);
24
-
25
- useEffect(
26
- () => analytics.productView(product.id, selectedVariant?.id),
27
- [analytics, product.id, selectedVariant?.id],
28
- );
29
-
30
- // A product without options has one variant; a product without variants is bought as itself.
31
- const variantId = product.variants.length > 0 ? (selectedVariant?.id ?? null) : null;
32
- const forSale =
33
- product.variants.length > 0
34
- ? Boolean(selectedVariant?.availableForSale)
35
- : product.availableForSale;
36
-
37
- async function add() {
38
- setError(null);
39
- try {
40
- await addLine({ productId: product.id, variantId, quantity: 1 });
41
- setAdded(true);
42
- } catch (e) {
43
- setError(e instanceof MagicStoreError ? (e.detail ?? e.message) : t.couldNotAdd);
44
- }
45
- }
46
-
47
- return (
48
- <div className="stack">
49
- <div className="row">
50
- <Money data={selectedVariant?.price ?? product.price} />
51
- {(selectedVariant?.compareAtPrice ?? product.compareAtPrice) && (
52
- <s className="muted">
53
- <Money data={selectedVariant?.compareAtPrice ?? product.compareAtPrice} />
54
- </s>
55
- )}
56
- </div>
57
- {product.options
58
- .filter((option) => option.values.length > 1)
59
- .map((option) => (
60
- <fieldset key={option.name}>
61
- <legend>{option.name}</legend>
62
- <div className="row">
63
- {option.values.map((value) => (
64
- <button
65
- key={value}
66
- type="button"
67
- aria-pressed={selectedOptions[option.name] === value}
68
- disabled={!isAvailable(option.name, value)}
69
- onClick={() => setOption(option.name, value)}
70
- >
71
- {value}
72
- </button>
73
- ))}
74
- </div>
75
- </fieldset>
76
- ))}
77
- <div className="row">
78
- <button className="primary" disabled={!forSale || status === 'updating'} onClick={add}>
79
- {forSale ? t.addToCart : t.soldOut}
80
- </button>
81
- <button type="button" onClick={() => void wishlist.toggle(product.id)}>
82
- {wishlist.has(product.id) ? `♥ ${t.saved}` : `♡ ${t.save}`}
83
- </button>
84
- </div>
85
- {added && <p className="muted">{t.addedToCart}</p>}
86
- {error && <p className="error">{error}</p>}
87
- </div>
88
- );
89
- }
@@ -1,15 +0,0 @@
1
- 'use client';
2
-
3
- import { useCart, useMagicStore } from '@magicstoreai/hydrogen';
4
- import Link from 'next/link';
5
- import { messages } from '@/lib/i18n';
6
-
7
- export function CartLink() {
8
- const { totalQuantity } = useCart();
9
- const t = messages(useMagicStore().locale);
10
- return (
11
- <Link href="/cart">
12
- {t.cart} ({totalQuantity})
13
- </Link>
14
- );
15
- }