@vitrine-kit/vitrine 0.4.3 → 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 -45
- 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 +46 -8
- package/kit/registry/cart/files/components/cart/AddToCart.tsx +38 -3
- 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 +9 -7
- package/kit/registry/catalog/feature.json +1 -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 +14 -3
- 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-stripe/feature.json +1 -1
- package/kit/registry/checkout-yookassa/feature.json +1 -1
- 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 +3 -3
- 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 +7 -3
- 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 +57 -6
- package/kit/templates/backend-payload/files/next.config.mjs +8 -2
- package/kit/templates/backend-payload/files/payload.config.ts +27 -3
- 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/base/files/.claude/commands/update.md +1 -1
- package/kit/templates/base/files/app/(frontend)/categories/[slug]/page.tsx +51 -4
- 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 +49 -4
- 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 +8 -2
- 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
1203
|
var CORE_RANGE = "^0.3.0";
|
|
1199
|
-
var BLUEPRINT_RANGE = "^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,17 +1398,17 @@ 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
|
-
"
|
|
1389
|
-
"
|
|
1390
|
-
"dev
|
|
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",
|
|
1411
|
+
"to the console once). Disable the fallback in dev too with `VITRINE_DB_STRICT=1`."
|
|
1391
1412
|
].join("\n");
|
|
1392
1413
|
const deploySecret = backend === "vendure" ? "export VENDURE_COOKIE_SECRET=... # Vendure cookie secret" : "export PAYLOAD_SECRET=... # random Payload secret";
|
|
1393
1414
|
return `# ${name}
|
|
@@ -1426,7 +1447,7 @@ vitrine design apply # style the new feature
|
|
|
1426
1447
|
## 5. Updates and checks
|
|
1427
1448
|
|
|
1428
1449
|
\`\`\`bash
|
|
1429
|
-
vitrine kit update # update the local registry/templates cache from
|
|
1450
|
+
vitrine kit update # update the local registry/templates cache from npm
|
|
1430
1451
|
vitrine diff <feature> # preview a feature update
|
|
1431
1452
|
vitrine update [feature] # 3-way merge of the new feature version (base = your snapshot)
|
|
1432
1453
|
vitrine doctor # consistency: vitrine.json \u2194 files \u2194 packages \u2194 env
|
|
@@ -1471,6 +1492,7 @@ function scaffoldBase(opts) {
|
|
|
1471
1492
|
`import type { SiteConfig } from '@vitrine-kit/contracts';
|
|
1472
1493
|
|
|
1473
1494
|
export const siteConfig: SiteConfig = {
|
|
1495
|
+
name: ${JSON.stringify(name)},
|
|
1474
1496
|
backend: ${JSON.stringify(backend)},
|
|
1475
1497
|
tier: ${JSON.stringify(tier)},
|
|
1476
1498
|
// vitrine:features:start
|
|
@@ -1511,7 +1533,7 @@ _No features installed yet._
|
|
|
1511
1533
|
| \`vitrine diff <feature>\` | Preview an update (without writing) | before \`update\` | \`--registry\` |
|
|
1512
1534
|
| \`vitrine doctor\` | Consistency: \`vitrine.json\` \u2194 files \u2194 packages \u2194 env | after edits, when in doubt | \`--registry\` |
|
|
1513
1535
|
| \`vitrine design apply\` | Apply the design from \`/design\` to tokens (via Claude Code) | after \`add\` or a rebrand | \`--bin\`, \`--dry-run\` |
|
|
1514
|
-
| \`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\` |
|
|
1515
1537
|
| \`vitrine kit status\` | Cache version vs the CLI's expected one | diagnostics | \u2014 |
|
|
1516
1538
|
| \`vitrine self-update\` | Update the CLI itself | rarely | \`--dry-run\` |
|
|
1517
1539
|
|
|
@@ -1559,6 +1581,7 @@ The step is idempotent: re-running converges and doesn't accumulate cruft.
|
|
|
1559
1581
|
writeText(join13(root, "lib", "blueprint.ts"), renderBlueprintFile([]));
|
|
1560
1582
|
writeText(join13(root, "theme", "client.css"), renderNeutralTheme());
|
|
1561
1583
|
writeText(join13(root, ".env.example"), clientEnvExample(backend));
|
|
1584
|
+
writeText(join13(root, ".env"), clientEnvFile(backend));
|
|
1562
1585
|
writeText(join13(root, "package.json"), `${JSON.stringify(clientPackageJson(name, backend), null, 2)}
|
|
1563
1586
|
`);
|
|
1564
1587
|
}
|
|
@@ -1573,43 +1596,89 @@ function initProject(opts) {
|
|
|
1573
1596
|
|
|
1574
1597
|
// src/kit-update.ts
|
|
1575
1598
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1576
|
-
import { existsSync as existsSync9, mkdtempSync,
|
|
1599
|
+
import { existsSync as existsSync9, mkdtempSync, rmSync as rmSync4 } from "fs";
|
|
1577
1600
|
import { tmpdir } from "os";
|
|
1578
|
-
import { join as join14, resolve as resolve6 } from "path";
|
|
1579
|
-
var
|
|
1580
|
-
function
|
|
1581
|
-
const
|
|
1582
|
-
|
|
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;
|
|
1583
1609
|
}
|
|
1584
|
-
function
|
|
1585
|
-
if (
|
|
1586
|
-
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
|
|
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
|
+
});
|
|
1590
1622
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
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"] }
|
|
1595
1657
|
);
|
|
1596
|
-
if (
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
if (spawnSync2("tar", ["-xzf", join14(tmp, tarball), "-C", tmp], { stdio: "inherit" }).status !== 0) {
|
|
1602
|
-
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
|
+
);
|
|
1603
1663
|
}
|
|
1604
|
-
const
|
|
1605
|
-
if (
|
|
1606
|
-
|
|
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
|
+
);
|
|
1607
1669
|
}
|
|
1608
1670
|
function warnOnVersionMismatch(root, requested) {
|
|
1609
1671
|
if (!requested) return;
|
|
1610
1672
|
const tagSemver = requested.match(/^@vitrine-kit\/vitrine@(.+)$/)?.[1] ?? requested.match(/^v?(\d+\.\d+\.\d+\S*)$/)?.[1];
|
|
1611
|
-
|
|
1612
|
-
|
|
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;
|
|
1613
1682
|
const version = readJson(pkgFile).version;
|
|
1614
1683
|
if (version && version !== tagSemver) {
|
|
1615
1684
|
console.warn(`[vitrine] requested "${requested}" but the downloaded kit is @vitrine-kit/vitrine@${version}`);
|
|
@@ -1621,7 +1690,7 @@ function kitUpdate(opts = {}) {
|
|
|
1621
1690
|
}
|
|
1622
1691
|
const tmp = mkdtempSync(join14(tmpdir(), "vitrine-kit-"));
|
|
1623
1692
|
try {
|
|
1624
|
-
const source =
|
|
1693
|
+
const source = acquireFromNpm(tmp, opts.version);
|
|
1625
1694
|
warnOnVersionMismatch(source, opts.version);
|
|
1626
1695
|
return populateCache(source, { home: opts.home, channel: opts.channel ?? "stable" });
|
|
1627
1696
|
} finally {
|
|
@@ -1647,7 +1716,7 @@ function selfUpdate(opts = {}) {
|
|
|
1647
1716
|
console.log(`[vitrine] npm ${args.join(" ")}`);
|
|
1648
1717
|
return 0;
|
|
1649
1718
|
}
|
|
1650
|
-
const res =
|
|
1719
|
+
const res = runNpm(args, { stdio: "inherit" });
|
|
1651
1720
|
if (res.error) throw res.error;
|
|
1652
1721
|
return res.status ?? 0;
|
|
1653
1722
|
}
|
|
@@ -1691,8 +1760,8 @@ design.command("apply").description("Apply the design from /design to the tokens
|
|
|
1691
1760
|
if (code !== 0) process.exit(code);
|
|
1692
1761
|
});
|
|
1693
1762
|
var kit = program.command("kit").description("Local tooling (the ~/.vitrine registry cache)");
|
|
1694
|
-
kit.command("update").description("Update the local registry/templates cache from
|
|
1695
|
-
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 });
|
|
1696
1765
|
console.log(`Cache updated to kit ${res.kitVersion}.`);
|
|
1697
1766
|
console.log(formatChangelog(res.changelog));
|
|
1698
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
|
+
}
|