create-magic-storefront 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/index.js +40 -3
- package/package.json +1 -1
- package/template/.claude/skills/storefront-design/SKILL.md +27 -22
- package/template/.claude/skills/storefront-verify/SKILL.md +10 -9
- package/template/AGENTS.md +16 -13
- package/template/PAGES.md +66 -11
- package/template/README.md +1 -1
- package/template/app/blog/[handle]/page.tsx +136 -0
- package/template/app/blog/author/[handle]/page.tsx +44 -0
- package/template/app/blog/category/[handle]/page.tsx +52 -0
- package/template/app/blog/page.tsx +97 -0
- package/template/app/blog/tag/[handle]/page.tsx +51 -0
- package/template/app/globals.css +68 -0
- package/template/app/layout.tsx +2 -0
- package/template/app/sitemap.ts +16 -6
- package/template/components/analytics-views.tsx +7 -0
- package/template/components/article-body.tsx +58 -0
- package/template/components/article-card.tsx +54 -0
- package/template/components/article-vote.tsx +43 -0
- package/template/components/blog-listing.tsx +93 -0
- package/template/components/pager.tsx +8 -2
- package/template/components/product-grid.tsx +27 -20
- package/template/lib/errors.ts +2 -1
- package/template/lib/i18n.ts +46 -0
- package/template/lib/site.ts +12 -0
- package/template/llms.txt +36 -10
package/template/lib/i18n.ts
CHANGED
|
@@ -57,6 +57,22 @@ const en = {
|
|
|
57
57
|
`${count} ${plural('en', count, { one: 'review', other: 'reviews' })}`,
|
|
58
58
|
recommendUs: (percent: number) => `${percent}% recommend us`,
|
|
59
59
|
leaveReview: 'Leave a review',
|
|
60
|
+
// Blog
|
|
61
|
+
blog: 'Blog',
|
|
62
|
+
allArticles: 'All articles',
|
|
63
|
+
featured: "Editor's picks",
|
|
64
|
+
readingTime: (minutes: number) => `${minutes} min read`,
|
|
65
|
+
relatedArticles: 'Read also',
|
|
66
|
+
productsInArticle: 'Products in this article',
|
|
67
|
+
wasHelpful: 'Was this article helpful?',
|
|
68
|
+
helpful: 'Yes',
|
|
69
|
+
notHelpful: 'No',
|
|
70
|
+
helpfulCount: (count: number) => `Helpful for ${count}`,
|
|
71
|
+
thanksForVote: 'Thank you for your feedback',
|
|
72
|
+
searchArticles: 'Search articles',
|
|
73
|
+
sortNewest: 'Newest',
|
|
74
|
+
sortPopular: 'Popular',
|
|
75
|
+
tag: 'Tag',
|
|
60
76
|
// Product
|
|
61
77
|
addToCart: 'Add to cart',
|
|
62
78
|
addedToCart: 'Added to cart.',
|
|
@@ -156,6 +172,21 @@ const ru: Messages = {
|
|
|
156
172
|
`${count} ${plural('ru', count, { one: 'отзыв', few: 'отзыва', other: 'отзывов' })}`,
|
|
157
173
|
recommendUs: (percent) => `${percent}% рекомендуют нас`,
|
|
158
174
|
leaveReview: 'Оставить отзыв',
|
|
175
|
+
blog: 'Блог',
|
|
176
|
+
allArticles: 'Все статьи',
|
|
177
|
+
featured: 'Выбор редакции',
|
|
178
|
+
readingTime: (minutes) => `${minutes} мин чтения`,
|
|
179
|
+
relatedArticles: 'Читайте также',
|
|
180
|
+
productsInArticle: 'Товары из статьи',
|
|
181
|
+
wasHelpful: 'Статья была полезной?',
|
|
182
|
+
helpful: 'Да',
|
|
183
|
+
notHelpful: 'Нет',
|
|
184
|
+
helpfulCount: (count) => `Полезно: ${count}`,
|
|
185
|
+
thanksForVote: 'Спасибо за отзыв',
|
|
186
|
+
searchArticles: 'Поиск по статьям',
|
|
187
|
+
sortNewest: 'Новые',
|
|
188
|
+
sortPopular: 'Популярные',
|
|
189
|
+
tag: 'Тег',
|
|
159
190
|
addToCart: 'В корзину',
|
|
160
191
|
addedToCart: 'Добавлено в корзину.',
|
|
161
192
|
couldNotAdd: 'Не удалось добавить в корзину.',
|
|
@@ -248,6 +279,21 @@ const uz: Messages = {
|
|
|
248
279
|
reviewsCount: (count) => `${count} ta sharh`,
|
|
249
280
|
recommendUs: (percent) => `${percent}% bizni tavsiya qiladi`,
|
|
250
281
|
leaveReview: 'Sharh qoldirish',
|
|
282
|
+
blog: 'Blog',
|
|
283
|
+
allArticles: 'Barcha maqolalar',
|
|
284
|
+
featured: 'Muharrir tanlovi',
|
|
285
|
+
readingTime: (minutes) => `${minutes} daqiqa o'qish`,
|
|
286
|
+
relatedArticles: "Shuningdek o'qing",
|
|
287
|
+
productsInArticle: 'Maqoladagi mahsulotlar',
|
|
288
|
+
wasHelpful: "Maqola foydali bo'ldimi?",
|
|
289
|
+
helpful: 'Ha',
|
|
290
|
+
notHelpful: "Yo'q",
|
|
291
|
+
helpfulCount: (count) => `Foydali: ${count}`,
|
|
292
|
+
thanksForVote: 'Fikringiz uchun rahmat',
|
|
293
|
+
searchArticles: 'Maqolalardan qidirish',
|
|
294
|
+
sortNewest: 'Yangilari',
|
|
295
|
+
sortPopular: 'Ommaboplari',
|
|
296
|
+
tag: 'Teg',
|
|
251
297
|
addToCart: "Savatga qo'shish",
|
|
252
298
|
addedToCart: "Savatga qo'shildi.",
|
|
253
299
|
couldNotAdd: "Savatga qo'shib bo'lmadi.",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Schema } from '@magicstoreai/storefront-client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The storefront's public origin for absolute URLs (sitemap, canonical links): `SITE_URL`, else
|
|
5
|
+
* the shop's primary domain.
|
|
6
|
+
*/
|
|
7
|
+
export function siteOrigin(shop: Pick<Schema<'Shop'>, 'primaryDomain'>): string {
|
|
8
|
+
return (
|
|
9
|
+
process.env.SITE_URL?.replace(/\/+$/, '') ??
|
|
10
|
+
`https://${shop.primaryDomain ?? process.env.MAGICSTORE_SHOP_DOMAIN}`
|
|
11
|
+
);
|
|
12
|
+
}
|
package/template/llms.txt
CHANGED
|
@@ -11,12 +11,15 @@ Every storefront — hand-written or generated — imports the SDK **only** thro
|
|
|
11
11
|
| ------------------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------- |
|
|
12
12
|
| `@magicstoreai/storefront-client` | `createStorefrontClient`: one typed method per API `operationId`, `MagicStoreError`, `paginate`, `Schema<>` | anywhere, no React |
|
|
13
13
|
| `@magicstoreai/hydrogen` | React: `MagicStoreProvider`, `useCart`, `useCustomer`, `useWishlist`, `<Money>`, `<Image>`, … | client (`"use client"`) |
|
|
14
|
-
| `@magicstoreai/hydrogen/core` | The same state without React: `CartController`, `CustomerSessionController`, `formatMoney`, variants
|
|
14
|
+
| `@magicstoreai/hydrogen/core` | The same state without React: `CartController`, `CustomerSessionController`, `formatMoney`, variants, blog | anywhere |
|
|
15
15
|
| `@magicstoreai/hydrogen/server` | `nextCacheFetch` (cache tags), `createWebhookHandler` (signed webhooks → revalidation) | server, edge |
|
|
16
|
-
| `@magicstoreai/hydrogen/seo` | `pageMeta`, `productJsonLd`, `breadcrumbJsonLd`, `jsonLdScript`
|
|
16
|
+
| `@magicstoreai/hydrogen/seo` | `pageMeta`, `productJsonLd`, `articleMeta`, `articleJsonLd`, `breadcrumbJsonLd`, `jsonLdScript` | anywhere |
|
|
17
17
|
|
|
18
18
|
Never import from `src/` or `dist/` of a package, and never call `/api/v2/storefront/…` by URL —
|
|
19
|
-
call the client method.
|
|
19
|
+
call the client method. A server component takes only components (`<Image>`, `<Money>`, …) from
|
|
20
|
+
`@magicstoreai/hydrogen`, a client module; functions and classes (`paginationState`,
|
|
21
|
+
`MagicStoreError`, `formatMoney`) come from `/core`, `/server`, `/seo` or
|
|
22
|
+
`@magicstoreai/storefront-client`. `npx create-magic-storefront check` fails on any of these.
|
|
20
23
|
|
|
21
24
|
## Start
|
|
22
25
|
|
|
@@ -24,15 +27,15 @@ call the client method. `npx create-magic-storefront check` fails on either.
|
|
|
24
27
|
npm create magic-storefront@latest my-shop -- --shop shop.example.uz
|
|
25
28
|
```
|
|
26
29
|
|
|
27
|
-
scaffolds a Next.js App Router storefront (home from the merchant's sections, catalog,
|
|
28
|
-
search, cart, checkout, sign-in, orders, SEO, webhook revalidation). Or install the packages:
|
|
30
|
+
scaffolds a Next.js App Router storefront (home from the merchant's sections or your own, catalog,
|
|
31
|
+
product, search, blog, cart, checkout, sign-in, orders, SEO, webhook revalidation). Or install the packages:
|
|
29
32
|
`npm i @magicstoreai/storefront-client @magicstoreai/hydrogen`.
|
|
30
33
|
|
|
31
34
|
## Docs
|
|
32
35
|
|
|
33
36
|
- `PAGES.md` (in a scaffolded storefront; `examples/starter/PAGES.md` in the SDK repository): how to
|
|
34
|
-
build each page type — home from the `/home` sections, collection, product, search,
|
|
35
|
-
checkout, account — with its data calls, cache tags, SEO and required states.
|
|
37
|
+
build each page type — home (from the `/home` sections or custom), collection, product, search,
|
|
38
|
+
blog, cart, checkout, account — with its data calls, cache tags, SEO and required states.
|
|
36
39
|
- [Hydrogen catalogue](node_modules/@magicstoreai/hydrogen/CATALOGUE.md): every export with its props
|
|
37
40
|
or signature, an example, and the API operations it calls. In the SDK repository:
|
|
38
41
|
`packages/hydrogen/CATALOGUE.md`.
|
|
@@ -40,7 +43,8 @@ search, cart, checkout, sign-in, orders, SEO, webhook revalidation). Or install
|
|
|
40
43
|
idempotency, retries, errors, pagination.
|
|
41
44
|
- [Hydrogen README](node_modules/@magicstoreai/hydrogen/README.md): provider, hooks, server helpers.
|
|
42
45
|
- The API itself (normative): the backend repository's `docs/api/v2/` — `standards.md` (wire format,
|
|
43
|
-
every error code), `authentication.md`, `cart.md`, `checkout.md`, `customer.md`, `
|
|
46
|
+
every error code), `authentication.md`, `cart.md`, `checkout.md`, `customer.md`, `blog.md`,
|
|
47
|
+
`webhooks.md`.
|
|
44
48
|
Operation shapes: `Schema<'Name'>` and the client's method types, generated from the OpenAPI spec.
|
|
45
49
|
|
|
46
50
|
## Two credentials
|
|
@@ -65,8 +69,30 @@ Public catalog reads need no customer. Render them on the server with a client w
|
|
|
65
69
|
- Lists paginate with `page` + `perPage` (default 24, max 100); read `meta.pagination`, or
|
|
66
70
|
`<Pagination>`. Sorts order (`sort=-createdAt`), filters narrow (`filter[onSale]=true`).
|
|
67
71
|
- A missing value is `null`, never `0` or `""`: show a fallback.
|
|
68
|
-
- The home page is `GET /home
|
|
69
|
-
|
|
72
|
+
- The home page is yours to design. `GET /home` (an ordered list of sections, one `type` each;
|
|
73
|
+
`GET /theme/section-schema` has every type's JSON Schema) is optional: render it to let the
|
|
74
|
+
merchant arrange the page from the admin, or compose a custom home from catalog and content
|
|
75
|
+
data. When you render it, an unknown type renders as nothing.
|
|
76
|
+
|
|
77
|
+
## Blog
|
|
78
|
+
|
|
79
|
+
- Only when `shop.features.blog` is on: at least one article is readable in the response language.
|
|
80
|
+
`shop.blog` is the blog home's `{ title, description }` or `null`.
|
|
81
|
+
- `blogArticlesIndex` (12 a page; `sort`, `q`, `filter[category|author|tag|featured]`),
|
|
82
|
+
`blogArticlesShow`, `blogArticlesRelated`, `blogCategoriesIndex`, `blogAuthorsIndex`. An unknown
|
|
83
|
+
`sort` or filter is `422 VALIDATION_FAILED`, never ignored.
|
|
84
|
+
- Articles are strict about language: one not written in the response locale is a `404`, with no
|
|
85
|
+
fallback. Build `hreflang` only from `availableLocales` (`articleMeta` does).
|
|
86
|
+
- `bodyHtml` is sanitized by the server. Split it with `articleBodyBlocks()` (`/core`, server-safe):
|
|
87
|
+
`<product-embed handle>` → a product card, `<oembed url>` → media, `<aside data-callout>` → a
|
|
88
|
+
callout. Fetch the embedded products in one `productsIndex({ query: { 'filter[handles]' } })` per
|
|
89
|
+
`embeddedProductFilters(article.products)` value, put them back in order with
|
|
90
|
+
`orderEmbeddedProducts`, and drop a marker whose product did not come back.
|
|
91
|
+
- «Was this helpful?»: `useArticleVote(handle, helpfulCount)`. A guest votes as the browser
|
|
92
|
+
(`X-Anonymous-Id`, `anonymousId()`), a customer as themselves. Report reads with
|
|
93
|
+
`useAnalytics().articleView(article.id)` (`ARTICLE_VIEW`).
|
|
94
|
+
- Blog reads are cached under `magicstore:pages` (`PAGES_UPDATED`); view and vote counters may be
|
|
95
|
+
up to 10 minutes old.
|
|
70
96
|
|
|
71
97
|
## Credentials in the browser
|
|
72
98
|
|