@vitrine-kit/vitrine 0.4.1 → 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.
- package/README.md +1 -1
- package/dist/index.js +114 -44
- package/kit/registry/_index.json +70 -9
- package/kit/registry/accounts/docs/accounts.md +20 -0
- package/kit/registry/accounts/feature.json +55 -0
- package/kit/registry/accounts/files/app/(frontend)/account/forgot-password/page.tsx +24 -0
- package/kit/registry/accounts/files/app/(frontend)/account/login/page.tsx +29 -0
- package/kit/registry/accounts/files/app/(frontend)/account/orders/page.tsx +90 -0
- package/kit/registry/accounts/files/app/(frontend)/account/page.tsx +86 -0
- package/kit/registry/accounts/files/app/(frontend)/account/register/page.tsx +24 -0
- package/kit/registry/accounts/files/app/(frontend)/account/reset-password/page.tsx +25 -0
- package/kit/registry/accounts/files/app/api/account/orders/route.ts +39 -0
- package/kit/registry/accounts/files/components/accounts/AccountLink.tsx +15 -0
- package/kit/registry/accounts/files/components/accounts/ForgotPasswordForm.tsx +90 -0
- package/kit/registry/accounts/files/components/accounts/LoginForm.tsx +84 -0
- package/kit/registry/accounts/files/components/accounts/LogoutButton.tsx +31 -0
- package/kit/registry/accounts/files/components/accounts/RegisterForm.tsx +108 -0
- package/kit/registry/accounts/files/components/accounts/ResetPasswordForm.tsx +84 -0
- package/kit/registry/accounts/files/lib/accounts/blueprint.ts +47 -0
- package/kit/registry/accounts/files/lib/accounts/register.ts +6 -0
- package/kit/registry/accounts/files/lib/accounts/session.ts +18 -0
- package/kit/registry/cart/feature.json +1 -1
- package/kit/registry/cart/files/app/api/cart/route.ts +101 -11
- package/kit/registry/cart/files/components/cart/AddToCart.tsx +142 -15
- package/kit/registry/cart/files/components/cart/CartIndicator.tsx +27 -2
- package/kit/registry/cart/files/components/cart/CartLineItem.tsx +71 -13
- package/kit/registry/cart/files/components/cart/CartView.tsx +21 -1
- package/kit/registry/catalog/feature.json +1 -1
- package/kit/registry/catalog/files/components/catalog/ProductCard.tsx +5 -1
- package/kit/registry/catalog/files/lib/catalog/data.ts +73 -5
- package/kit/registry/checkout/docs/checkout.md +19 -1
- package/kit/registry/checkout/feature.json +1 -1
- package/kit/registry/checkout/files/app/(frontend)/order/success/page.tsx +49 -0
- package/kit/registry/checkout/files/app/api/checkout/route.ts +25 -4
- package/kit/registry/checkout/files/components/checkout/CheckoutButton.tsx +5 -3
- package/kit/registry/checkout/files/lib/checkout/fulfill.ts +22 -0
- package/kit/registry/checkout/files/lib/checkout/notify.ts +52 -0
- package/kit/registry/checkout-paddle/feature.json +1 -1
- package/kit/registry/checkout-paddle/files/app/api/webhooks/paddle/route.ts +9 -2
- package/kit/registry/checkout-stripe/feature.json +1 -1
- package/kit/registry/checkout-stripe/files/app/api/webhooks/stripe/route.ts +9 -2
- package/kit/registry/checkout-yookassa/feature.json +1 -1
- package/kit/registry/checkout-yookassa/files/app/api/webhooks/yookassa/route.ts +9 -2
- package/kit/registry/email/docs/email.md +19 -0
- package/kit/registry/email/feature.json +60 -0
- package/kit/registry/email/files/lib/email/adapter.ts +89 -0
- package/kit/registry/filters/docs/filters.md +13 -0
- package/kit/registry/filters/feature.json +45 -0
- package/kit/registry/filters/files/components/filters/CatalogToolbar.tsx +123 -0
- package/kit/registry/filters/files/lib/filters/register.ts +6 -0
- package/kit/registry/i18n/docs/i18n.md +24 -0
- package/kit/registry/i18n/feature.json +47 -0
- package/kit/registry/i18n/files/components/i18n/LocaleSwitcher.tsx +64 -0
- package/kit/registry/i18n/files/lib/i18n/dictionary.ts +52 -0
- package/kit/registry/i18n/files/lib/i18n/locale.ts +11 -0
- package/kit/registry/i18n/files/lib/i18n/register.ts +6 -0
- package/kit/registry/i18n/files/lib/i18n/t.ts +1 -0
- package/kit/registry/i18n/files/lib/i18n/useChromeLabel.ts +28 -0
- package/kit/registry/i18n/files/middleware.ts +51 -0
- package/kit/registry/product-page/feature.json +1 -1
- package/kit/registry/product-page/files/components/product/ProductGallery.tsx +23 -10
- package/kit/registry/product-page/files/components/product/ProductView.tsx +8 -4
- package/kit/registry/reviews/docs/reviews.md +11 -0
- package/kit/registry/reviews/feature.json +55 -0
- package/kit/registry/reviews/files/app/api/reviews/route.ts +55 -0
- package/kit/registry/reviews/files/components/reviews/ReviewList.tsx +119 -0
- package/kit/registry/reviews/files/lib/reviews/blueprint.ts +13 -0
- package/kit/registry/reviews/files/lib/reviews/register.ts +6 -0
- package/kit/registry/reviews/files/lib/reviews/types.ts +22 -0
- package/kit/registry/search/docs/search.md +12 -0
- package/kit/registry/search/feature.json +49 -0
- package/kit/registry/search/files/app/(frontend)/search/page.tsx +34 -0
- package/kit/registry/search/files/components/search/SearchForm.tsx +32 -0
- package/kit/registry/search/files/components/search/SearchResults.tsx +87 -0
- package/kit/registry/search/files/lib/search/data.ts +8 -0
- package/kit/registry/search/files/lib/search/register.ts +8 -0
- package/kit/registry/seo/docs/seo.md +4 -1
- package/kit/registry/seo/feature.json +5 -1
- package/kit/registry/seo/files/app/robots.ts +13 -0
- package/kit/registry/seo/files/app/sitemap.ts +27 -0
- package/kit/registry/stubs/route-glue.d.ts +71 -0
- package/kit/registry/tsconfig.json +7 -2
- package/kit/registry/wishlist/docs/wishlist.md +11 -0
- package/kit/registry/wishlist/feature.json +55 -0
- package/kit/registry/wishlist/files/app/(frontend)/wishlist/page.tsx +16 -0
- package/kit/registry/wishlist/files/components/wishlist/WishlistButton.tsx +37 -0
- package/kit/registry/wishlist/files/components/wishlist/WishlistIndicator.tsx +30 -0
- package/kit/registry/wishlist/files/components/wishlist/WishlistView.tsx +73 -0
- package/kit/registry/wishlist/files/lib/wishlist/register.ts +8 -0
- package/kit/registry/wishlist/files/lib/wishlist/storage.ts +30 -0
- package/kit/templates/backend-payload/files/Dockerfile +3 -0
- package/kit/templates/backend-payload/files/docker-compose.yml +8 -0
- package/kit/templates/backend-payload/files/lib/adapter/payload-catalog.ts +51 -17
- package/kit/templates/backend-payload/files/lib/adapter/payload-commerce.ts +18 -0
- package/kit/templates/backend-payload/files/lib/adapter/product-query.ts +126 -0
- package/kit/templates/backend-payload/files/lib/seed/admin.ts +16 -6
- package/kit/templates/backend-payload/files/lib/seed/demo.ts +74 -28
- package/kit/templates/backend-payload/files/lib/seed/guards.ts +17 -4
- package/kit/templates/backend-payload/files/lib/seed/locales.ts +53 -0
- package/kit/templates/backend-payload/files/lib/seed/run.ts +92 -16
- package/kit/templates/backend-payload/files/next.config.mjs +34 -0
- package/kit/templates/backend-payload/files/payload.config.ts +33 -3
- package/kit/templates/backend-payload/files/seed-assets/placeholder-1b.svg +6 -0
- package/kit/templates/backend-payload/files/seed-assets/placeholder-2b.svg +6 -0
- package/kit/templates/backend-vendure/files/Dockerfile +1 -1
- package/kit/templates/backend-vendure/files/docker-compose.yml +2 -1
- package/kit/templates/backend-vendure/files/lib/adapter/product-query.ts +126 -0
- package/kit/templates/backend-vendure/files/lib/adapter/vendure-catalog.ts +18 -11
- package/kit/templates/backend-vendure/files/vendure-config.ts +38 -1
- package/kit/templates/base/files/.claude/commands/update.md +1 -1
- package/kit/templates/base/files/app/(frontend)/categories/[slug]/page.tsx +57 -5
- package/kit/templates/base/files/app/(frontend)/layout.tsx +13 -7
- package/kit/templates/base/files/app/(frontend)/not-found.tsx +26 -0
- package/kit/templates/base/files/app/(frontend)/page.tsx +84 -6
- package/kit/templates/base/files/app/(frontend)/products/[slug]/page.tsx +5 -2
- package/kit/templates/base/files/app/globals.css +13 -0
- package/kit/templates/base/files/components/SlotsProvider.tsx +19 -0
- package/kit/templates/base/files/components/layout/SiteFooter.tsx +6 -1
- package/kit/templates/base/files/components/layout/SiteHeader.tsx +8 -2
- package/kit/templates/base/files/lib/i18n/dictionary.ts +36 -0
- package/kit/templates/base/files/lib/i18n/locale.ts +11 -0
- package/kit/templates/base/files/lib/i18n/useChromeLabel.ts +28 -0
- package/kit/templates/base/files/lib/site.ts +3 -0
- package/kit/templates/base/files/next.config.mjs +34 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -31,4 +31,4 @@ with flags is in the generated project's `CLAUDE.md`.
|
|
|
31
31
|
|
|
32
32
|
At its core is the **feature install primitive**, shared by `init` and `add` (guarantee: "added by
|
|
33
33
|
the wizard" ≡ "added later"). `add`/`update` work offline from the `~/.vitrine` cache; the network
|
|
34
|
-
is only needed for `kit update
|
|
34
|
+
is only needed for `kit update` (public npm — `@vitrine-kit/vitrine`).
|
package/dist/index.js
CHANGED
|
@@ -363,9 +363,13 @@ function activePaymentProvider(features) {
|
|
|
363
363
|
}
|
|
364
364
|
function renderIntegrationsRegion(features) {
|
|
365
365
|
const payments = activePaymentProvider(features);
|
|
366
|
-
|
|
366
|
+
const email = features.some((f) => f.name === "email") ? "smtp" : void 0;
|
|
367
|
+
const lines = [];
|
|
368
|
+
if (payments) lines.push(` payments: ${JSON.stringify(payments)},`);
|
|
369
|
+
if (email) lines.push(` email: ${JSON.stringify(email)},`);
|
|
370
|
+
if (lines.length === 0) return " integrations: {},";
|
|
367
371
|
return ` integrations: {
|
|
368
|
-
|
|
372
|
+
${lines.join("\n")}
|
|
369
373
|
},`;
|
|
370
374
|
}
|
|
371
375
|
function assertNoPascalCollisions(features) {
|
|
@@ -1188,15 +1192,16 @@ function diffFeatureCmd(name, registryRoot, projectRoot) {
|
|
|
1188
1192
|
}
|
|
1189
1193
|
|
|
1190
1194
|
// src/init.ts
|
|
1195
|
+
import { randomBytes } from "crypto";
|
|
1191
1196
|
import { readdirSync as readdirSync4 } from "fs";
|
|
1192
1197
|
import { join as join13 } from "path";
|
|
1193
1198
|
|
|
1194
1199
|
// src/kit-versions.generated.ts
|
|
1195
|
-
var KIT_VERSION = "0.
|
|
1200
|
+
var KIT_VERSION = "0.5.0";
|
|
1196
1201
|
var CONTRACTS_VERSION = "1.0.0";
|
|
1197
1202
|
var CONTRACTS_RANGE = "^1.0.0";
|
|
1198
|
-
var CORE_RANGE = "^0.
|
|
1199
|
-
var BLUEPRINT_RANGE = "^0.
|
|
1203
|
+
var CORE_RANGE = "^0.3.0";
|
|
1204
|
+
var BLUEPRINT_RANGE = "^0.3.0";
|
|
1200
1205
|
|
|
1201
1206
|
// src/kit.ts
|
|
1202
1207
|
var REACT_RANGE = "^18.3.1";
|
|
@@ -1230,7 +1235,7 @@ var PAYMENT_PROVIDER_FEATURES = [
|
|
|
1230
1235
|
"checkout-yookassa"
|
|
1231
1236
|
];
|
|
1232
1237
|
function suggestFeatures(tier, registry, backend = defaultBackend(tier)) {
|
|
1233
|
-
const core = ["catalog", "product-page", "seo"];
|
|
1238
|
+
const core = ["catalog", "product-page", "seo", "search", "filters"];
|
|
1234
1239
|
const shop = backend === "vendure" ? ["cart"] : ["cart", "checkout-stripe"];
|
|
1235
1240
|
const desired = tier === "catalog" ? core : [...core, ...shop];
|
|
1236
1241
|
return desired.filter((name) => registry.hasFeature(name));
|
|
@@ -1350,10 +1355,14 @@ function clientEnvExample(backend) {
|
|
|
1350
1355
|
"# Payload secret (required; generate a random one for prod).",
|
|
1351
1356
|
"PAYLOAD_SECRET=",
|
|
1352
1357
|
"",
|
|
1353
|
-
"# Dev admin (created
|
|
1358
|
+
"# Dev / bootstrap admin (created when the users collection is empty in dev,",
|
|
1359
|
+
"# or in production when SEED_ON_BOOT=1 \u2014 password printed to the console).",
|
|
1354
1360
|
"DEV_ADMIN_EMAIL=",
|
|
1355
1361
|
"DEV_ADMIN_PASSWORD=",
|
|
1356
1362
|
"",
|
|
1363
|
+
"# Docker compose: seed demo catalog + admin on first boot (default 1 in compose).",
|
|
1364
|
+
"# SEED_ON_BOOT=1",
|
|
1365
|
+
"",
|
|
1357
1366
|
"# Site base URL (canonical, OG).",
|
|
1358
1367
|
"NEXT_PUBLIC_SITE_URL=http://localhost:3000",
|
|
1359
1368
|
"",
|
|
@@ -1362,6 +1371,18 @@ function clientEnvExample(backend) {
|
|
|
1362
1371
|
""
|
|
1363
1372
|
].join("\n");
|
|
1364
1373
|
}
|
|
1374
|
+
function clientEnvFile(backend) {
|
|
1375
|
+
const example = clientEnvExample(backend);
|
|
1376
|
+
if (backend === "payload") {
|
|
1377
|
+
const secret = randomBytes(24).toString("base64url");
|
|
1378
|
+
return example.replace(/^PAYLOAD_SECRET=$/m, `PAYLOAD_SECRET=${secret}`);
|
|
1379
|
+
}
|
|
1380
|
+
if (backend === "vendure") {
|
|
1381
|
+
const secret = randomBytes(24).toString("base64url");
|
|
1382
|
+
return example.replace(/^VENDURE_COOKIE_SECRET=$/m, `VENDURE_COOKIE_SECRET=${secret}`);
|
|
1383
|
+
}
|
|
1384
|
+
return example;
|
|
1385
|
+
}
|
|
1365
1386
|
function clientReadme(name, backend, tier) {
|
|
1366
1387
|
const run = backend === "vendure" ? [
|
|
1367
1388
|
"```bash",
|
|
@@ -1377,15 +1398,16 @@ function clientReadme(name, backend, tier) {
|
|
|
1377
1398
|
].join("\n") : [
|
|
1378
1399
|
"```bash",
|
|
1379
1400
|
"pnpm install",
|
|
1380
|
-
"cp .env.example .env",
|
|
1381
1401
|
"pnpm dev",
|
|
1382
1402
|
"```",
|
|
1383
1403
|
"",
|
|
1384
1404
|
"- Storefront: http://localhost:3000",
|
|
1385
1405
|
"- Admin: http://localhost:3000/admin",
|
|
1386
1406
|
"",
|
|
1387
|
-
"
|
|
1388
|
-
"
|
|
1407
|
+
"`vitrine init` writes a starter `.env` (including `PAYLOAD_SECRET`). Copy from",
|
|
1408
|
+
"`.env.example` only if you need a fresh file. Without Postgres, dev starts a built-in",
|
|
1409
|
+
"SQLite (`.vitrine/dev.sqlite`), seeds a demo catalog (5 products, 2 categories,",
|
|
1410
|
+
"size/color variants, gallery images) and creates a dev admin (login/password printed",
|
|
1389
1411
|
"to the console once). Disable the fallback in dev too with `VITRINE_DB_STRICT=1`."
|
|
1390
1412
|
].join("\n");
|
|
1391
1413
|
const deploySecret = backend === "vendure" ? "export VENDURE_COOKIE_SECRET=... # Vendure cookie secret" : "export PAYLOAD_SECRET=... # random Payload secret";
|
|
@@ -1425,7 +1447,7 @@ vitrine design apply # style the new feature
|
|
|
1425
1447
|
## 5. Updates and checks
|
|
1426
1448
|
|
|
1427
1449
|
\`\`\`bash
|
|
1428
|
-
vitrine kit update # update the local registry/templates cache from
|
|
1450
|
+
vitrine kit update # update the local registry/templates cache from npm
|
|
1429
1451
|
vitrine diff <feature> # preview a feature update
|
|
1430
1452
|
vitrine update [feature] # 3-way merge of the new feature version (base = your snapshot)
|
|
1431
1453
|
vitrine doctor # consistency: vitrine.json \u2194 files \u2194 packages \u2194 env
|
|
@@ -1470,6 +1492,7 @@ function scaffoldBase(opts) {
|
|
|
1470
1492
|
`import type { SiteConfig } from '@vitrine-kit/contracts';
|
|
1471
1493
|
|
|
1472
1494
|
export const siteConfig: SiteConfig = {
|
|
1495
|
+
name: ${JSON.stringify(name)},
|
|
1473
1496
|
backend: ${JSON.stringify(backend)},
|
|
1474
1497
|
tier: ${JSON.stringify(tier)},
|
|
1475
1498
|
// vitrine:features:start
|
|
@@ -1510,7 +1533,7 @@ _No features installed yet._
|
|
|
1510
1533
|
| \`vitrine diff <feature>\` | Preview an update (without writing) | before \`update\` | \`--registry\` |
|
|
1511
1534
|
| \`vitrine doctor\` | Consistency: \`vitrine.json\` \u2194 files \u2194 packages \u2194 env | after edits, when in doubt | \`--registry\` |
|
|
1512
1535
|
| \`vitrine design apply\` | Apply the design from \`/design\` to tokens (via Claude Code) | after \`add\` or a rebrand | \`--bin\`, \`--dry-run\` |
|
|
1513
|
-
| \`vitrine kit update\` | Update the local registry/templates cache from
|
|
1536
|
+
| \`vitrine kit update\` | Update the local registry/templates cache from npm | before updating features | \`[version]\`, \`--from\`, \`--channel\` |
|
|
1514
1537
|
| \`vitrine kit status\` | Cache version vs the CLI's expected one | diagnostics | \u2014 |
|
|
1515
1538
|
| \`vitrine self-update\` | Update the CLI itself | rarely | \`--dry-run\` |
|
|
1516
1539
|
|
|
@@ -1558,6 +1581,7 @@ The step is idempotent: re-running converges and doesn't accumulate cruft.
|
|
|
1558
1581
|
writeText(join13(root, "lib", "blueprint.ts"), renderBlueprintFile([]));
|
|
1559
1582
|
writeText(join13(root, "theme", "client.css"), renderNeutralTheme());
|
|
1560
1583
|
writeText(join13(root, ".env.example"), clientEnvExample(backend));
|
|
1584
|
+
writeText(join13(root, ".env"), clientEnvFile(backend));
|
|
1561
1585
|
writeText(join13(root, "package.json"), `${JSON.stringify(clientPackageJson(name, backend), null, 2)}
|
|
1562
1586
|
`);
|
|
1563
1587
|
}
|
|
@@ -1572,43 +1596,89 @@ function initProject(opts) {
|
|
|
1572
1596
|
|
|
1573
1597
|
// src/kit-update.ts
|
|
1574
1598
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1575
|
-
import { existsSync as existsSync9, mkdtempSync,
|
|
1599
|
+
import { existsSync as existsSync9, mkdtempSync, rmSync as rmSync4 } from "fs";
|
|
1576
1600
|
import { tmpdir } from "os";
|
|
1577
|
-
import { join as join14, resolve as resolve6 } from "path";
|
|
1578
|
-
var
|
|
1579
|
-
function
|
|
1580
|
-
const
|
|
1581
|
-
|
|
1601
|
+
import { dirname as dirname7, join as join14, resolve as resolve6 } from "path";
|
|
1602
|
+
var NPM_PKG = "@vitrine-kit/vitrine";
|
|
1603
|
+
function resolveNpmCli() {
|
|
1604
|
+
const candidates = [
|
|
1605
|
+
join14(dirname7(process.execPath), "node_modules", "npm", "bin", "npm-cli.js"),
|
|
1606
|
+
join14(dirname7(process.execPath), "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
|
|
1607
|
+
];
|
|
1608
|
+
return candidates.find((p2) => existsSync9(p2)) ?? null;
|
|
1582
1609
|
}
|
|
1583
|
-
function
|
|
1584
|
-
if (
|
|
1585
|
-
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1610
|
+
function hasNpm() {
|
|
1611
|
+
if (resolveNpmCli()) return true;
|
|
1612
|
+
const probe = process.platform === "win32" ? "where" : "which";
|
|
1613
|
+
return spawnSync2(probe, ["npm"], { stdio: "ignore", shell: process.platform === "win32" }).status === 0;
|
|
1614
|
+
}
|
|
1615
|
+
function runNpm(args, opts = {}) {
|
|
1616
|
+
const cli = resolveNpmCli();
|
|
1617
|
+
if (cli) {
|
|
1618
|
+
return spawnSync2(process.execPath, [cli, ...args], {
|
|
1619
|
+
encoding: opts.encoding,
|
|
1620
|
+
stdio: opts.stdio ?? "inherit"
|
|
1621
|
+
});
|
|
1589
1622
|
}
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1623
|
+
return spawnSync2("npm", args, {
|
|
1624
|
+
encoding: opts.encoding,
|
|
1625
|
+
stdio: opts.stdio ?? "inherit",
|
|
1626
|
+
shell: process.platform === "win32"
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
function resolveKitNpmSpec(version) {
|
|
1630
|
+
if (!version || version === "latest") return `${NPM_PKG}@latest`;
|
|
1631
|
+
if (version.startsWith(`${NPM_PKG}@`)) return version;
|
|
1632
|
+
if (version.startsWith("@") && version.includes("/")) return version;
|
|
1633
|
+
const semver = version.replace(/^v/, "");
|
|
1634
|
+
return `${NPM_PKG}@${semver}`;
|
|
1635
|
+
}
|
|
1636
|
+
function acquireFromNpm(tmp, version) {
|
|
1637
|
+
if (!hasNpm()) {
|
|
1638
|
+
throw new Error("[vitrine] network update needs npm, or pass --from <dir>");
|
|
1639
|
+
}
|
|
1640
|
+
const spec = resolveKitNpmSpec(version);
|
|
1641
|
+
if (!/^[@A-Za-z0-9_./-]+$/.test(spec)) {
|
|
1642
|
+
throw new Error(`[vitrine] invalid kit version/spec: ${version}`);
|
|
1643
|
+
}
|
|
1644
|
+
const install = runNpm(
|
|
1645
|
+
[
|
|
1646
|
+
"install",
|
|
1647
|
+
spec,
|
|
1648
|
+
"--prefix",
|
|
1649
|
+
tmp,
|
|
1650
|
+
"--no-save",
|
|
1651
|
+
"--ignore-scripts",
|
|
1652
|
+
"--no-package-lock",
|
|
1653
|
+
"--registry",
|
|
1654
|
+
"https://registry.npmjs.org/"
|
|
1655
|
+
],
|
|
1656
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
|
|
1594
1657
|
);
|
|
1595
|
-
if (
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
if (spawnSync2("tar", ["-xzf", join14(tmp, tarball), "-C", tmp], { stdio: "inherit" }).status !== 0) {
|
|
1601
|
-
throw new Error("[vitrine] tarball extraction failed");
|
|
1658
|
+
if (install.status !== 0) {
|
|
1659
|
+
const err = (install.stderr || install.stdout || install.error?.message || "").toString().trim();
|
|
1660
|
+
throw new Error(
|
|
1661
|
+
`[vitrine] npm install ${spec} failed${err ? `: ${err}` : ""} \u2014 check https://www.npmjs.com/package/@vitrine-kit/vitrine`
|
|
1662
|
+
);
|
|
1602
1663
|
}
|
|
1603
|
-
const
|
|
1604
|
-
if (
|
|
1605
|
-
|
|
1664
|
+
const kitRoot = join14(tmp, "node_modules", "@vitrine-kit", "vitrine", "kit");
|
|
1665
|
+
if (existsSync9(join14(kitRoot, "registry", "_index.json"))) return kitRoot;
|
|
1666
|
+
throw new Error(
|
|
1667
|
+
"[vitrine] installed package has no kit/registry/_index.json \u2014 reinstall @vitrine-kit/vitrine or pass --from <dir>"
|
|
1668
|
+
);
|
|
1606
1669
|
}
|
|
1607
1670
|
function warnOnVersionMismatch(root, requested) {
|
|
1608
1671
|
if (!requested) return;
|
|
1609
1672
|
const tagSemver = requested.match(/^@vitrine-kit\/vitrine@(.+)$/)?.[1] ?? requested.match(/^v?(\d+\.\d+\.\d+\S*)$/)?.[1];
|
|
1610
|
-
|
|
1611
|
-
|
|
1673
|
+
if (!tagSemver || tagSemver === "latest") return;
|
|
1674
|
+
const pkgCandidates = [
|
|
1675
|
+
join14(root, "..", "package.json"),
|
|
1676
|
+
// …/vitrine/kit → …/vitrine/package.json
|
|
1677
|
+
join14(root, "packages", "cli", "package.json"),
|
|
1678
|
+
join14(root, "package.json")
|
|
1679
|
+
];
|
|
1680
|
+
const pkgFile = pkgCandidates.find((p2) => exists(p2));
|
|
1681
|
+
if (!pkgFile) return;
|
|
1612
1682
|
const version = readJson(pkgFile).version;
|
|
1613
1683
|
if (version && version !== tagSemver) {
|
|
1614
1684
|
console.warn(`[vitrine] requested "${requested}" but the downloaded kit is @vitrine-kit/vitrine@${version}`);
|
|
@@ -1620,7 +1690,7 @@ function kitUpdate(opts = {}) {
|
|
|
1620
1690
|
}
|
|
1621
1691
|
const tmp = mkdtempSync(join14(tmpdir(), "vitrine-kit-"));
|
|
1622
1692
|
try {
|
|
1623
|
-
const source =
|
|
1693
|
+
const source = acquireFromNpm(tmp, opts.version);
|
|
1624
1694
|
warnOnVersionMismatch(source, opts.version);
|
|
1625
1695
|
return populateCache(source, { home: opts.home, channel: opts.channel ?? "stable" });
|
|
1626
1696
|
} finally {
|
|
@@ -1646,7 +1716,7 @@ function selfUpdate(opts = {}) {
|
|
|
1646
1716
|
console.log(`[vitrine] npm ${args.join(" ")}`);
|
|
1647
1717
|
return 0;
|
|
1648
1718
|
}
|
|
1649
|
-
const res =
|
|
1719
|
+
const res = runNpm(args, { stdio: "inherit" });
|
|
1650
1720
|
if (res.error) throw res.error;
|
|
1651
1721
|
return res.status ?? 0;
|
|
1652
1722
|
}
|
|
@@ -1690,8 +1760,8 @@ design.command("apply").description("Apply the design from /design to the tokens
|
|
|
1690
1760
|
if (code !== 0) process.exit(code);
|
|
1691
1761
|
});
|
|
1692
1762
|
var kit = program.command("kit").description("Local tooling (the ~/.vitrine registry cache)");
|
|
1693
|
-
kit.command("update").description("Update the local registry/templates cache from
|
|
1694
|
-
const res = kitUpdate({ from: opts.from, version
|
|
1763
|
+
kit.command("update").description("Update the local registry/templates cache from npm (or --from <dir>)").argument("[version]", "npm version or dist-tag (default: latest)").option("--from <path>", "local kit tree (clone / unpacked package) instead of the network").option("--channel <channel>", "stable | main", "stable").action((version, opts) => {
|
|
1764
|
+
const res = kitUpdate({ from: opts.from, version, channel: opts.channel });
|
|
1695
1765
|
console.log(`Cache updated to kit ${res.kitVersion}.`);
|
|
1696
1766
|
console.log(formatChangelog(res.changelog));
|
|
1697
1767
|
});
|
package/kit/registry/_index.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "../schemas/registry-index.schema.json",
|
|
3
|
-
"kitVersion": "0.
|
|
3
|
+
"kitVersion": "0.5.0",
|
|
4
4
|
"contracts": "1.0.0",
|
|
5
5
|
"features": {
|
|
6
6
|
"catalog": {
|
|
7
7
|
"title": "Product catalog",
|
|
8
|
-
"kitVersion": "0.
|
|
8
|
+
"kitVersion": "0.5.0",
|
|
9
9
|
"tier": [
|
|
10
10
|
"catalog",
|
|
11
11
|
"simple-store",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"product-page": {
|
|
16
16
|
"title": "Product page",
|
|
17
|
-
"kitVersion": "0.
|
|
17
|
+
"kitVersion": "0.5.0",
|
|
18
18
|
"tier": [
|
|
19
19
|
"catalog",
|
|
20
20
|
"simple-store",
|
|
@@ -23,7 +23,52 @@
|
|
|
23
23
|
},
|
|
24
24
|
"seo": {
|
|
25
25
|
"title": "SEO — metadata and JSON-LD",
|
|
26
|
-
"kitVersion": "0.
|
|
26
|
+
"kitVersion": "0.5.0",
|
|
27
|
+
"tier": [
|
|
28
|
+
"catalog",
|
|
29
|
+
"simple-store",
|
|
30
|
+
"full-store"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"search": {
|
|
34
|
+
"title": "Product search",
|
|
35
|
+
"kitVersion": "0.5.0",
|
|
36
|
+
"tier": [
|
|
37
|
+
"catalog",
|
|
38
|
+
"simple-store",
|
|
39
|
+
"full-store"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"filters": {
|
|
43
|
+
"title": "Catalog sort and filters",
|
|
44
|
+
"kitVersion": "0.5.0",
|
|
45
|
+
"tier": [
|
|
46
|
+
"catalog",
|
|
47
|
+
"simple-store",
|
|
48
|
+
"full-store"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"wishlist": {
|
|
52
|
+
"title": "Wishlist",
|
|
53
|
+
"kitVersion": "0.5.0",
|
|
54
|
+
"tier": [
|
|
55
|
+
"catalog",
|
|
56
|
+
"simple-store",
|
|
57
|
+
"full-store"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"reviews": {
|
|
61
|
+
"title": "Product reviews",
|
|
62
|
+
"kitVersion": "0.5.0",
|
|
63
|
+
"tier": [
|
|
64
|
+
"catalog",
|
|
65
|
+
"simple-store",
|
|
66
|
+
"full-store"
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
"i18n": {
|
|
70
|
+
"title": "Locale switcher and path prefixes",
|
|
71
|
+
"kitVersion": "0.5.0",
|
|
27
72
|
"tier": [
|
|
28
73
|
"catalog",
|
|
29
74
|
"simple-store",
|
|
@@ -32,7 +77,7 @@
|
|
|
32
77
|
},
|
|
33
78
|
"cart": {
|
|
34
79
|
"title": "Cart",
|
|
35
|
-
"kitVersion": "0.
|
|
80
|
+
"kitVersion": "0.5.0",
|
|
36
81
|
"tier": [
|
|
37
82
|
"simple-store",
|
|
38
83
|
"full-store"
|
|
@@ -40,7 +85,23 @@
|
|
|
40
85
|
},
|
|
41
86
|
"checkout": {
|
|
42
87
|
"title": "Checkout",
|
|
43
|
-
"kitVersion": "0.
|
|
88
|
+
"kitVersion": "0.5.0",
|
|
89
|
+
"tier": [
|
|
90
|
+
"simple-store",
|
|
91
|
+
"full-store"
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
"accounts": {
|
|
95
|
+
"title": "Accounts — login and order history",
|
|
96
|
+
"kitVersion": "0.5.0",
|
|
97
|
+
"tier": [
|
|
98
|
+
"simple-store",
|
|
99
|
+
"full-store"
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
"email": {
|
|
103
|
+
"title": "Transactional email",
|
|
104
|
+
"kitVersion": "0.5.0",
|
|
44
105
|
"tier": [
|
|
45
106
|
"simple-store",
|
|
46
107
|
"full-store"
|
|
@@ -48,7 +109,7 @@
|
|
|
48
109
|
},
|
|
49
110
|
"checkout-stripe": {
|
|
50
111
|
"title": "Payment — Stripe",
|
|
51
|
-
"kitVersion": "0.
|
|
112
|
+
"kitVersion": "0.5.0",
|
|
52
113
|
"tier": [
|
|
53
114
|
"simple-store",
|
|
54
115
|
"full-store"
|
|
@@ -56,7 +117,7 @@
|
|
|
56
117
|
},
|
|
57
118
|
"checkout-paddle": {
|
|
58
119
|
"title": "Payment — Paddle",
|
|
59
|
-
"kitVersion": "0.
|
|
120
|
+
"kitVersion": "0.5.0",
|
|
60
121
|
"tier": [
|
|
61
122
|
"simple-store",
|
|
62
123
|
"full-store"
|
|
@@ -64,7 +125,7 @@
|
|
|
64
125
|
},
|
|
65
126
|
"checkout-yookassa": {
|
|
66
127
|
"title": "Payment — YooKassa",
|
|
67
|
-
"kitVersion": "0.
|
|
128
|
+
"kitVersion": "0.5.0",
|
|
68
129
|
"tier": [
|
|
69
130
|
"simple-store",
|
|
70
131
|
"full-store"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Feature: accounts
|
|
2
|
+
|
|
3
|
+
Customer accounts (Payload `customers` auth collection) plus guest order lookup by email.
|
|
4
|
+
|
|
5
|
+
## Modules
|
|
6
|
+
- `customers` collection — added via blueprint `addCollection` (email/password auth)
|
|
7
|
+
- `orders.customer` — optional relationship field (set on webhook fulfill when email matches)
|
|
8
|
+
- `/account` — signed-in dashboard or sign-in / register / guest lookup
|
|
9
|
+
- `/account/login`, `/account/register`
|
|
10
|
+
- `/account/forgot-password`, `/account/reset-password?token=`
|
|
11
|
+
- `/account/orders` — orders for the session email or `?email=` guest query
|
|
12
|
+
- `AccountLink` — header link
|
|
13
|
+
|
|
14
|
+
## Auth
|
|
15
|
+
Uses Payload's built-in REST: `POST /api/customers`, `/login`, `/logout`,
|
|
16
|
+
`/forgot-password`, `/reset-password`. Session cookies are managed by Payload.
|
|
17
|
+
Forgot-password emails go through the Payload email adapter (`email` feature / console).
|
|
18
|
+
|
|
19
|
+
## Guest lookup
|
|
20
|
+
Still available without an account — enter the checkout email on `/account`.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../schemas/feature.schema.json",
|
|
3
|
+
"name": "accounts",
|
|
4
|
+
"title": "Accounts — login and order history",
|
|
5
|
+
"kitVersion": "0.5.0",
|
|
6
|
+
"requiresContracts": ">=1.0.0 <2.0.0",
|
|
7
|
+
"tier": [
|
|
8
|
+
"simple-store",
|
|
9
|
+
"full-store"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"checkout"
|
|
13
|
+
],
|
|
14
|
+
"corePackages": {
|
|
15
|
+
"@vitrine-kit/core": ">=0.1.0",
|
|
16
|
+
"@vitrine-kit/payload-blueprint": ">=0.2.0"
|
|
17
|
+
},
|
|
18
|
+
"npm": [],
|
|
19
|
+
"files": [
|
|
20
|
+
{
|
|
21
|
+
"from": "files/components/accounts/",
|
|
22
|
+
"to": "components/accounts/"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"from": "files/lib/accounts/",
|
|
26
|
+
"to": "lib/accounts/"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"from": "files/app/",
|
|
30
|
+
"to": "app/"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"config": {
|
|
34
|
+
"set": {
|
|
35
|
+
"features.accounts": true
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"slots": [
|
|
39
|
+
{
|
|
40
|
+
"slot": "global.header-actions",
|
|
41
|
+
"component": "AccountLink",
|
|
42
|
+
"order": 25
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"blueprint": {
|
|
46
|
+
"extend": "order",
|
|
47
|
+
"addFields": [
|
|
48
|
+
"customer"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"env": [],
|
|
52
|
+
"claudeDoc": "docs/accounts.md",
|
|
53
|
+
"conflicts": [],
|
|
54
|
+
"removable": true
|
|
55
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import { ForgotPasswordForm } from '@/components/accounts/ForgotPasswordForm';
|
|
3
|
+
|
|
4
|
+
export const metadata: Metadata = {
|
|
5
|
+
title: 'Forgot password',
|
|
6
|
+
robots: { index: false, follow: false },
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function ForgotPasswordPage() {
|
|
10
|
+
return (
|
|
11
|
+
<div className="flex max-w-md flex-col gap-section">
|
|
12
|
+
<header className="flex flex-col gap-unit">
|
|
13
|
+
<h1 className="font-heading text-fg">Forgot password</h1>
|
|
14
|
+
<p className="text-muted-fg">
|
|
15
|
+
Enter your account email. We send a reset link when the address is registered.
|
|
16
|
+
</p>
|
|
17
|
+
</header>
|
|
18
|
+
<ForgotPasswordForm />
|
|
19
|
+
<a href="/account/login" className="w-fit text-fg underline underline-offset-2">
|
|
20
|
+
Back to sign in
|
|
21
|
+
</a>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import { LoginForm } from '@/components/accounts/LoginForm';
|
|
3
|
+
|
|
4
|
+
export const metadata: Metadata = {
|
|
5
|
+
title: 'Sign in',
|
|
6
|
+
robots: { index: false, follow: false },
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function AccountLoginPage() {
|
|
10
|
+
return (
|
|
11
|
+
<div className="flex max-w-md flex-col gap-section">
|
|
12
|
+
<header className="flex flex-col gap-unit">
|
|
13
|
+
<h1 className="font-heading text-fg">Sign in</h1>
|
|
14
|
+
<p className="text-muted-fg">
|
|
15
|
+
No account yet?{' '}
|
|
16
|
+
<a href="/account/register" className="underline underline-offset-2">
|
|
17
|
+
Create one
|
|
18
|
+
</a>
|
|
19
|
+
</p>
|
|
20
|
+
</header>
|
|
21
|
+
<LoginForm />
|
|
22
|
+
<p className="text-sm text-muted-fg">
|
|
23
|
+
<a href="/account/forgot-password" className="underline underline-offset-2">
|
|
24
|
+
Forgot password?
|
|
25
|
+
</a>
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import { getPayload } from 'payload';
|
|
3
|
+
import config from '@payload-config';
|
|
4
|
+
import { formatMoney } from '@/lib/cart/data';
|
|
5
|
+
import { getCustomer } from '@/lib/accounts/session';
|
|
6
|
+
import { LogoutButton } from '@/components/accounts/LogoutButton';
|
|
7
|
+
|
|
8
|
+
export const metadata: Metadata = {
|
|
9
|
+
title: 'Your orders',
|
|
10
|
+
robots: { index: false, follow: false },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
interface PageProps {
|
|
14
|
+
searchParams: Promise<{ email?: string }>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default async function AccountOrdersPage({ searchParams }: PageProps) {
|
|
18
|
+
const customer = await getCustomer();
|
|
19
|
+
const { email: raw } = await searchParams;
|
|
20
|
+
const guestEmail = raw?.trim().toLowerCase() ?? '';
|
|
21
|
+
const email = customer?.email?.trim().toLowerCase() || guestEmail;
|
|
22
|
+
|
|
23
|
+
if (!email?.includes('@')) {
|
|
24
|
+
return (
|
|
25
|
+
<div className="flex flex-col gap-gutter">
|
|
26
|
+
<h1 className="font-heading text-fg">Your orders</h1>
|
|
27
|
+
<p className="text-muted-fg">Sign in or provide a valid email on the account page.</p>
|
|
28
|
+
<a href="/account" className="w-fit text-fg underline underline-offset-2">
|
|
29
|
+
Back
|
|
30
|
+
</a>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const payload = await getPayload({ config });
|
|
36
|
+
const res = await payload.find({
|
|
37
|
+
collection: 'orders',
|
|
38
|
+
where: { email: { equals: email } },
|
|
39
|
+
sort: '-createdAt',
|
|
40
|
+
limit: 50,
|
|
41
|
+
overrideAccess: true,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const orders = res.docs as Array<{
|
|
45
|
+
id: string | number;
|
|
46
|
+
status?: string;
|
|
47
|
+
total?: number;
|
|
48
|
+
currency?: string;
|
|
49
|
+
createdAt?: string;
|
|
50
|
+
paymentRef?: string;
|
|
51
|
+
}>;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className="flex flex-col gap-section">
|
|
55
|
+
<header className="flex flex-col gap-unit">
|
|
56
|
+
<h1 className="font-heading text-fg">Your orders</h1>
|
|
57
|
+
<p className="text-muted-fg">
|
|
58
|
+
Showing orders for <span className="text-fg">{email}</span>
|
|
59
|
+
{customer ? ' (signed in)' : ' (guest lookup)'}
|
|
60
|
+
</p>
|
|
61
|
+
</header>
|
|
62
|
+
{orders.length === 0 ? (
|
|
63
|
+
<p className="text-muted-fg">No orders found for that email.</p>
|
|
64
|
+
) : (
|
|
65
|
+
<ul role="list" className="flex flex-col gap-gutter">
|
|
66
|
+
{orders.map((o) => (
|
|
67
|
+
<li key={String(o.id)} className="flex flex-col gap-unit border-b border-border py-unit">
|
|
68
|
+
<p className="text-fg">
|
|
69
|
+
Order #{String(o.id)} — {o.status ?? 'unknown'}
|
|
70
|
+
</p>
|
|
71
|
+
<p className="text-muted-fg">
|
|
72
|
+
{typeof o.total === 'number' ? formatMoney(o.total, o.currency ?? 'USD') : '—'}
|
|
73
|
+
{o.createdAt ? ` · ${new Date(o.createdAt).toLocaleString()}` : ''}
|
|
74
|
+
</p>
|
|
75
|
+
{o.paymentRef ? (
|
|
76
|
+
<p className="text-sm text-muted-fg">Ref: {o.paymentRef}</p>
|
|
77
|
+
) : null}
|
|
78
|
+
</li>
|
|
79
|
+
))}
|
|
80
|
+
</ul>
|
|
81
|
+
)}
|
|
82
|
+
<div className="flex flex-wrap gap-gutter">
|
|
83
|
+
<a href="/account" className="w-fit text-fg underline underline-offset-2">
|
|
84
|
+
Account
|
|
85
|
+
</a>
|
|
86
|
+
{customer ? <LogoutButton /> : null}
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|