@cogenta/starters 0.1.5
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/LICENSE +373 -0
- package/dist/blueprints/assets/photos/association/food-bank.jpg +0 -0
- package/dist/blueprints/assets/photos/association/fundraising-dinner.jpg +0 -0
- package/dist/blueprints/assets/photos/association/garden-planting.jpg +0 -0
- package/dist/blueprints/assets/photos/association/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/association/homework-club.jpg +0 -0
- package/dist/blueprints/assets/photos/association/partner-ashworth-college.png +0 -0
- package/dist/blueprints/assets/photos/association/partner-ashworth-council.png +0 -0
- package/dist/blueprints/assets/photos/association/partner-brindle-and-sons.png +0 -0
- package/dist/blueprints/assets/photos/association/partner-kestrel-homes.png +0 -0
- package/dist/blueprints/assets/photos/association/partner-linden-trust.png +0 -0
- package/dist/blueprints/assets/photos/association/partner-marlow-street-pharmacy.png +0 -0
- package/dist/blueprints/assets/photos/association/volunteer-orientation.jpg +0 -0
- package/dist/blueprints/assets/photos/association/volunteer-portrait.jpg +0 -0
- package/dist/blueprints/assets/photos/association/winter-coat-collection.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/desk-setup.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/notebook-and-coffee.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/notebooks.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/platform.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/pour-over.jpg +0 -0
- package/dist/blueprints/assets/photos/documentation/delivery-flow.png +0 -0
- package/dist/blueprints/assets/photos/magazine/archive-room.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/banquet-hall.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/city-hall.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/dining-room.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/downtown-towers.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/gallery-opening.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/garden-volunteers.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/station-platform.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/theater-stage.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/fenmore-branch.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/fenmore-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/fenmore-stationery.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/gannet-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/gannet-sardines.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/gannet-shelf.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/hollis-crane-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/hollis-crane-grid.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/hollis-crane-salt-year.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/hollis-crane-spines.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/rookery-hall-brochure.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/rookery-hall-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/rookery-hall-poster-carnival.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/rookery-hall-poster-sibelius.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/rookery-hall-series.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-gate.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-pictograms.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-totem.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/tidewater-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/tidewater-data.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/tidewater-report-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/warp-and-weft-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/warp-and-weft-room.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/westmoor-rail-components.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/westmoor-rail-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/westmoor-rail-departures.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/beetroot-goats-curd.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/ceps-risotto.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/chocolate-tart.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/duck-leg-red-cabbage.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/grilled-octopus.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/hake-green-peppercorns.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/poached-pear.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/vegetable-broth.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/adrian-tan.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/api-webhooks.png +0 -0
- package/dist/blueprints/assets/photos/saas/approvals-queue.png +0 -0
- package/dist/blueprints/assets/photos/saas/audit-log.png +0 -0
- package/dist/blueprints/assets/photos/saas/erp-sync.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-brightwell-clinics.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-castlemere-foods.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-halvorsen-freight.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-northgate-homes.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-orrin-and-vale.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-tessaly-energy.png +0 -0
- package/dist/blueprints/assets/photos/saas/policy-editor.png +0 -0
- package/dist/blueprints/assets/photos/saas/spend-reporting.png +0 -0
- package/dist/blueprints/assets/photos/saas/sso-scim.png +0 -0
- package/dist/blueprints/assets/photos/store/brushed-cotton-overshirt.jpg +0 -0
- package/dist/blueprints/assets/photos/store/cable-knit-beanie.jpg +0 -0
- package/dist/blueprints/assets/photos/store/canvas-shoulder-bag.jpg +0 -0
- package/dist/blueprints/assets/photos/store/canvas-tote.jpg +0 -0
- package/dist/blueprints/assets/photos/store/cast-iron-skillet.jpg +0 -0
- package/dist/blueprints/assets/photos/store/enamel-mug.jpg +0 -0
- package/dist/blueprints/assets/photos/store/field-jacket.jpg +0 -0
- package/dist/blueprints/assets/photos/store/heavyweight-t-shirt.jpg +0 -0
- package/dist/blueprints/assets/photos/store/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/store/leather-card-holder.jpg +0 -0
- package/dist/blueprints/assets/photos/store/linen-table-runner.jpg +0 -0
- package/dist/blueprints/assets/photos/store/porcelain-pour-over-set.jpg +0 -0
- package/dist/blueprints/assets/photos/store/striped-wool-blanket.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/avatar-1.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/avatar-2.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/avatar-3.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/case-council.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/case-hotels.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/case-rail.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/exhibit-working-capital.png +0 -0
- package/dist/blueprints/assets/photos/vitrine/hero-review.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/logo-aldermoor.png +0 -0
- package/dist/blueprints/assets/photos/vitrine/logo-brightwater.png +0 -0
- package/dist/blueprints/assets/photos/vitrine/logo-castell-vane.png +0 -0
- package/dist/blueprints/assets/photos/vitrine/logo-meridian-rail.png +0 -0
- package/dist/blueprints/assets/photos/vitrine/logo-nordvik.png +0 -0
- package/dist/blueprints/assets/photos/vitrine/logo-oakfield-health.png +0 -0
- package/dist/blueprints/association.d.ts +322 -0
- package/dist/blueprints/association.d.ts.map +1 -0
- package/dist/blueprints/association.js +1440 -0
- package/dist/blueprints/association.js.map +1 -0
- package/dist/blueprints/blog.d.ts +221 -0
- package/dist/blueprints/blog.d.ts.map +1 -0
- package/dist/blueprints/blog.js +1132 -0
- package/dist/blueprints/blog.js.map +1 -0
- package/dist/blueprints/content-pack.d.ts +148 -0
- package/dist/blueprints/content-pack.d.ts.map +1 -0
- package/dist/blueprints/content-pack.js +111 -0
- package/dist/blueprints/content-pack.js.map +1 -0
- package/dist/blueprints/content-packs.d.ts +12 -0
- package/dist/blueprints/content-packs.d.ts.map +1 -0
- package/dist/blueprints/content-packs.js +30 -0
- package/dist/blueprints/content-packs.js.map +1 -0
- package/dist/blueprints/demo-media.d.ts +39 -0
- package/dist/blueprints/demo-media.d.ts.map +1 -0
- package/dist/blueprints/demo-media.js +31 -0
- package/dist/blueprints/demo-media.js.map +1 -0
- package/dist/blueprints/documentation.d.ts +165 -0
- package/dist/blueprints/documentation.d.ts.map +1 -0
- package/dist/blueprints/documentation.js +1236 -0
- package/dist/blueprints/documentation.js.map +1 -0
- package/dist/blueprints/magazine.d.ts +207 -0
- package/dist/blueprints/magazine.d.ts.map +1 -0
- package/dist/blueprints/magazine.js +1076 -0
- package/dist/blueprints/magazine.js.map +1 -0
- package/dist/blueprints/menus.d.ts +25 -0
- package/dist/blueprints/menus.d.ts.map +1 -0
- package/dist/blueprints/menus.js +33 -0
- package/dist/blueprints/menus.js.map +1 -0
- package/dist/blueprints/photo-assets.d.ts +8 -0
- package/dist/blueprints/photo-assets.d.ts.map +1 -0
- package/dist/blueprints/photo-assets.js +41 -0
- package/dist/blueprints/photo-assets.js.map +1 -0
- package/dist/blueprints/portfolio.d.ts +207 -0
- package/dist/blueprints/portfolio.d.ts.map +1 -0
- package/dist/blueprints/portfolio.js +1032 -0
- package/dist/blueprints/portfolio.js.map +1 -0
- package/dist/blueprints/restaurant.d.ts +191 -0
- package/dist/blueprints/restaurant.d.ts.map +1 -0
- package/dist/blueprints/restaurant.js +1076 -0
- package/dist/blueprints/restaurant.js.map +1 -0
- package/dist/blueprints/saas.d.ts +268 -0
- package/dist/blueprints/saas.d.ts.map +1 -0
- package/dist/blueprints/saas.js +1472 -0
- package/dist/blueprints/saas.js.map +1 -0
- package/dist/blueprints/site-settings-seed.d.ts +17 -0
- package/dist/blueprints/site-settings-seed.d.ts.map +1 -0
- package/dist/blueprints/site-settings-seed.js +45 -0
- package/dist/blueprints/site-settings-seed.js.map +1 -0
- package/dist/blueprints/starting-skins.d.ts +19 -0
- package/dist/blueprints/starting-skins.d.ts.map +1 -0
- package/dist/blueprints/starting-skins.js +310 -0
- package/dist/blueprints/starting-skins.js.map +1 -0
- package/dist/blueprints/store.d.ts +290 -0
- package/dist/blueprints/store.d.ts.map +1 -0
- package/dist/blueprints/store.js +1031 -0
- package/dist/blueprints/store.js.map +1 -0
- package/dist/blueprints/vitrine.d.ts +266 -0
- package/dist/blueprints/vitrine.d.ts.map +1 -0
- package/dist/blueprints/vitrine.js +1056 -0
- package/dist/blueprints/vitrine.js.map +1 -0
- package/dist/blueprints/widgets.d.ts +26 -0
- package/dist/blueprints/widgets.d.ts.map +1 -0
- package/dist/blueprints/widgets.js +27 -0
- package/dist/blueprints/widgets.js.map +1 -0
- package/dist/demo-art/compositions.d.ts +48 -0
- package/dist/demo-art/compositions.d.ts.map +1 -0
- package/dist/demo-art/compositions.js +690 -0
- package/dist/demo-art/compositions.js.map +1 -0
- package/dist/demo-art/index.d.ts +8 -0
- package/dist/demo-art/index.d.ts.map +1 -0
- package/dist/demo-art/index.js +5 -0
- package/dist/demo-art/index.js.map +1 -0
- package/dist/demo-art/oklch.d.ts +39 -0
- package/dist/demo-art/oklch.d.ts.map +1 -0
- package/dist/demo-art/oklch.js +79 -0
- package/dist/demo-art/oklch.js.map +1 -0
- package/dist/demo-art/png.d.ts +24 -0
- package/dist/demo-art/png.d.ts.map +1 -0
- package/dist/demo-art/png.js +110 -0
- package/dist/demo-art/png.js.map +1 -0
- package/dist/demo-art/render.d.ts +228 -0
- package/dist/demo-art/render.d.ts.map +1 -0
- package/dist/demo-art/render.js +478 -0
- package/dist/demo-art/render.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,1031 @@
|
|
|
1
|
+
import { CogentaError } from '@cogenta/core';
|
|
2
|
+
import { createContentStore, defineCollection, f, validateCollectionSet, } from '@cogenta/schema';
|
|
3
|
+
import { coverArt, heroArt, productArt } from '../demo-art/compositions.js';
|
|
4
|
+
import { definePageCollection, SEO_FIELDS, toBlockZoneEntry, } from './content-pack.js';
|
|
5
|
+
import { STARTING_SKINS } from './starting-skins.js';
|
|
6
|
+
/**
|
|
7
|
+
* The `store` blueprint (L27, `@cogenta/theme-ecommerce`): a small brand of
|
|
8
|
+
* durable everyday goods with a shop and a repair bench in Lisbon, twelve
|
|
9
|
+
* products from the workshops it buys from, four categories, and the pages a
|
|
10
|
+
* real shop needs before it takes an order (how to order, delivery and
|
|
11
|
+
* returns, repairs, contact, terms).
|
|
12
|
+
*
|
|
13
|
+
* **Contract A only, and no checkout.** A blueprint writes
|
|
14
|
+
* `cogenta.schema.mjs` and seeds content; it cannot provision
|
|
15
|
+
* `@cogenta/commerce`'s tables (contract E, ADR-0024), and contract B has no
|
|
16
|
+
* cart or checkout block. So the catalogue is honest about how to buy: each
|
|
17
|
+
* product names where it can be ordered (`orderLink`, an email to the shop
|
|
18
|
+
* with the product in the subject line), the theme turns that into an
|
|
19
|
+
* "Order by email" action, and the "How to order" page says what happens
|
|
20
|
+
* next. A shop that connects a payment link or a marketplace page puts that
|
|
21
|
+
* address in the same field.
|
|
22
|
+
*
|
|
23
|
+
* Categories are a small collection of their own rather than a taxonomy: a
|
|
24
|
+
* category page here is a photographed grid of goods, and a term archive
|
|
25
|
+
* (`TermArchiveEntry`) carries neither a picture nor a price. Each product
|
|
26
|
+
* keeps its category as a plain `select`, which is what a category's own
|
|
27
|
+
* list filters on and what the product page prints.
|
|
28
|
+
*/
|
|
29
|
+
export const DEFAULT_SHOP_NAME = 'Atelier Goods';
|
|
30
|
+
const CATEGORY_NAMES = ['Wear', 'Carry', 'Kitchen', 'Living'];
|
|
31
|
+
const CURRENCIES = ['EUR', 'GBP', 'CHF', 'DKK', 'SEK', 'USD'];
|
|
32
|
+
const PERMISSIONS = {
|
|
33
|
+
read: ['public'],
|
|
34
|
+
create: ['editor', 'admin'],
|
|
35
|
+
update: ['editor', 'admin'],
|
|
36
|
+
delete: ['admin'],
|
|
37
|
+
publish: ['admin'],
|
|
38
|
+
};
|
|
39
|
+
export const product = defineCollection({
|
|
40
|
+
name: 'product',
|
|
41
|
+
labels: { singular: 'Product', plural: 'Products' },
|
|
42
|
+
routing: { pattern: '/shop/:slug' },
|
|
43
|
+
fields: {
|
|
44
|
+
name: f.text({ required: true, max: 120 }),
|
|
45
|
+
slug: f.slug({ from: 'name', unique: true }),
|
|
46
|
+
description: f.text({
|
|
47
|
+
max: 300,
|
|
48
|
+
multiline: true,
|
|
49
|
+
admin: { label: 'Short description', help: 'Shown beside the price on the product page.' },
|
|
50
|
+
}),
|
|
51
|
+
price: f.number({ required: true, min: 0 }),
|
|
52
|
+
currency: f.select({ options: [...CURRENCIES], default: 'EUR' }),
|
|
53
|
+
category: f.select({ options: [...CATEGORY_NAMES], required: true }),
|
|
54
|
+
inStock: f.boolean({ default: true, admin: { label: 'In stock' } }),
|
|
55
|
+
material: f.text({ max: 200 }),
|
|
56
|
+
dimensions: f.text({ max: 200 }),
|
|
57
|
+
weight: f.text({ max: 60 }),
|
|
58
|
+
capacity: f.text({ max: 60 }),
|
|
59
|
+
origin: f.text({ max: 160, admin: { label: 'Made in' } }),
|
|
60
|
+
care: f.text({ max: 300, multiline: true }),
|
|
61
|
+
delivery: f.text({
|
|
62
|
+
max: 300,
|
|
63
|
+
multiline: true,
|
|
64
|
+
admin: { label: 'Delivery note', help: 'One or two sentences under the order button.' },
|
|
65
|
+
}),
|
|
66
|
+
orderLink: f.text({
|
|
67
|
+
max: 400,
|
|
68
|
+
admin: {
|
|
69
|
+
label: 'Where to order',
|
|
70
|
+
help: 'An email link (mailto:), a payment link or a marketplace page. The product page shows it as the order button.',
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
73
|
+
photo: f.media({ accept: ['image'] }),
|
|
74
|
+
blocks: f.blocks(),
|
|
75
|
+
...SEO_FIELDS,
|
|
76
|
+
},
|
|
77
|
+
indexes: [['slug'], ['category']],
|
|
78
|
+
permissions: PERMISSIONS,
|
|
79
|
+
});
|
|
80
|
+
export const category = defineCollection({
|
|
81
|
+
name: 'category',
|
|
82
|
+
labels: { singular: 'Category', plural: 'Categories' },
|
|
83
|
+
routing: { pattern: '/category/:slug' },
|
|
84
|
+
fields: {
|
|
85
|
+
name: f.text({ required: true, max: 80 }),
|
|
86
|
+
slug: f.slug({ from: 'name', unique: true }),
|
|
87
|
+
summary: f.text({ max: 240, multiline: true }),
|
|
88
|
+
photo: f.media({ accept: ['image'] }),
|
|
89
|
+
blocks: f.blocks(),
|
|
90
|
+
...SEO_FIELDS,
|
|
91
|
+
},
|
|
92
|
+
indexes: [['slug']],
|
|
93
|
+
permissions: PERMISSIONS,
|
|
94
|
+
});
|
|
95
|
+
export const page = definePageCollection('/:slug');
|
|
96
|
+
export const STORE_COLLECTIONS = [product, category, page];
|
|
97
|
+
validateCollectionSet(STORE_COLLECTIONS);
|
|
98
|
+
const BLOCK_VERSION = '1.0.0';
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Copy helpers
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
/** `orders@ateliergoods.com` for "Atelier Goods": the shop's own name, as a mailbox. */
|
|
103
|
+
export function shopEmail(siteName, mailbox = 'hello') {
|
|
104
|
+
const domain = siteName
|
|
105
|
+
.normalize('NFKD')
|
|
106
|
+
.replace(/[̀-ͯ]/g, '')
|
|
107
|
+
.toLowerCase()
|
|
108
|
+
.replace(/[^a-z0-9]+/g, '');
|
|
109
|
+
return `${mailbox}@${domain === '' ? 'shop' : domain}.com`;
|
|
110
|
+
}
|
|
111
|
+
function nameOf(siteName) {
|
|
112
|
+
const trimmed = siteName?.trim();
|
|
113
|
+
return trimmed === undefined || trimmed === '' ? DEFAULT_SHOP_NAME : trimmed;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* A paragraph where `[words](href)` becomes a link. Nothing else is
|
|
117
|
+
* interpreted: the copy stays plain text a person can read in this file.
|
|
118
|
+
*/
|
|
119
|
+
function textBlock(key, style, text) {
|
|
120
|
+
const parts = text.split(/(\[[^\]]+\]\([^)]+\))/).filter((part) => part !== '');
|
|
121
|
+
const markDefs = [];
|
|
122
|
+
const children = parts.map((part, index) => {
|
|
123
|
+
const link = /^\[([^\]]+)\]\(([^)]+)\)$/.exec(part);
|
|
124
|
+
if (link === null) {
|
|
125
|
+
return { _key: `${key}-s${index}`, _type: 'span', text: part, marks: [] };
|
|
126
|
+
}
|
|
127
|
+
const mark = `${key}-l${index}`;
|
|
128
|
+
markDefs.push({ _key: mark, _type: 'link', href: link[2] });
|
|
129
|
+
return {
|
|
130
|
+
_key: `${key}-s${index}`,
|
|
131
|
+
_type: 'span',
|
|
132
|
+
text: link[1],
|
|
133
|
+
marks: [mark],
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
return { _key: key, _type: 'block', style, children, markDefs };
|
|
137
|
+
}
|
|
138
|
+
/** A prose block from lines: `## ` starts a heading, anything else is a paragraph. */
|
|
139
|
+
function prose(key, lines) {
|
|
140
|
+
return {
|
|
141
|
+
_key: key,
|
|
142
|
+
_type: 'prose',
|
|
143
|
+
_version: BLOCK_VERSION,
|
|
144
|
+
body: lines.map((line, index) => line.startsWith('## ')
|
|
145
|
+
? textBlock(`${key}-${index}`, 'h2', line.slice(3))
|
|
146
|
+
: textBlock(`${key}-${index}`, 'normal', line)),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function answer(key, text) {
|
|
150
|
+
return [textBlock(key, 'normal', text)];
|
|
151
|
+
}
|
|
152
|
+
function questions(key, items) {
|
|
153
|
+
return items.map(([question, text], index) => ({
|
|
154
|
+
_key: `${key}-${index}`,
|
|
155
|
+
question,
|
|
156
|
+
answer: answer(`${key}-${index}-a`, text),
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
const SHIPS = 'Ships from Lisbon within two working days. Free returns within 30 days.';
|
|
160
|
+
/**
|
|
161
|
+
* Twelve products, three per category and two sold out, written oldest
|
|
162
|
+
* first: the newest four (the jacket, the shoulder bag, the pour-over set and
|
|
163
|
+
* the blanket) open the home page's grid. Each photograph is the product it
|
|
164
|
+
* names, and every material, size and origin matches what the picture shows.
|
|
165
|
+
*/
|
|
166
|
+
export const STORE_DEMO_PRODUCTS = [
|
|
167
|
+
{
|
|
168
|
+
name: 'Linen table runner',
|
|
169
|
+
slug: 'linen-table-runner',
|
|
170
|
+
category: 'Living',
|
|
171
|
+
price: 68,
|
|
172
|
+
inStock: false,
|
|
173
|
+
description: 'A long runner in washed white linen with knotted fringes, woven narrow enough to leave room for plates on either side.',
|
|
174
|
+
material: 'Linen, stone washed.',
|
|
175
|
+
dimensions: '45 × 200 cm, fringes included.',
|
|
176
|
+
origin: 'Guimarães, Portugal',
|
|
177
|
+
care: 'Wash at 40 °C and line dry. Linen looks best a little creased.',
|
|
178
|
+
delivery: 'The next weaving is due in April. Write to us and we will keep one aside for you.',
|
|
179
|
+
story: [
|
|
180
|
+
'Woven in Guimarães from flax grown in Normandy and spun in the north of Portugal. The ends are left to fray for a few centimetres and then knotted by hand, so there is no hem to pucker in the wash.',
|
|
181
|
+
'Stone washing softens the cloth before it reaches you. It turns whiter and more supple each time it is washed, and red wine comes out with cold water if you get to it the same evening.',
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: 'Enamel mug',
|
|
186
|
+
slug: 'enamel-mug',
|
|
187
|
+
category: 'Kitchen',
|
|
188
|
+
price: 24,
|
|
189
|
+
inStock: true,
|
|
190
|
+
description: 'A 350 ml mug in forest green enamel over steel, with a rolled stainless rim. Light enough for a rucksack and good enough for the table.',
|
|
191
|
+
material: 'Enamelled steel, stainless steel rim.',
|
|
192
|
+
capacity: '350 ml',
|
|
193
|
+
dimensions: '8.5 cm across, 8 cm high.',
|
|
194
|
+
origin: 'Olkusz, Poland',
|
|
195
|
+
care: 'Dishwasher safe. A chip in the enamel does not spread, so keep using it.',
|
|
196
|
+
delivery: SHIPS,
|
|
197
|
+
story: [
|
|
198
|
+
'Pressed and enamelled in Olkusz, a town that has made enamelware since the beginning of the last century. Each mug is dipped twice and fired at 800 °C, which gives the green its depth and a surface that coffee will not stain.',
|
|
199
|
+
'It can go on a camping stove and in the oven. We drink from these at the shop counter every morning, and some of ours have been in daily use for six years.',
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'Cable-knit beanie',
|
|
204
|
+
slug: 'cable-knit-beanie',
|
|
205
|
+
category: 'Wear',
|
|
206
|
+
price: 42,
|
|
207
|
+
inStock: true,
|
|
208
|
+
description: 'A deep, loose beanie in charcoal lambswool with a wide turn-up. Knitted on hand-flat machines and finished by hand.',
|
|
209
|
+
material: 'Lambswool, charcoal melange.',
|
|
210
|
+
dimensions: 'One size, 28 cm high unfolded.',
|
|
211
|
+
origin: 'Covilhã, Portugal',
|
|
212
|
+
care: 'Hand wash cold with wool soap and dry flat on a towel.',
|
|
213
|
+
delivery: SHIPS,
|
|
214
|
+
story: [
|
|
215
|
+
'Knitted in Covilhã, a town that once ran on its wool mills and still has a handful of knitters working hand-flat machines. The cable is set loose, so the hat rests on the head instead of gripping it.',
|
|
216
|
+
'Lambswool pills a little in the first weeks of wear. A wool comb takes it off, and the hat stays soft for years afterwards.',
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'Canvas tote',
|
|
221
|
+
slug: 'canvas-tote',
|
|
222
|
+
category: 'Carry',
|
|
223
|
+
price: 48,
|
|
224
|
+
inStock: true,
|
|
225
|
+
description: 'An undyed cotton canvas tote with handles long enough to wear over a coat. It stands up on its own and carries a week of shopping.',
|
|
226
|
+
material: 'Cotton canvas, 14 oz, undyed.',
|
|
227
|
+
dimensions: '38 × 42 × 12 cm. Handles 62 cm.',
|
|
228
|
+
origin: 'Lisbon, Portugal',
|
|
229
|
+
care: 'Machine wash cold, reshape while damp and dry flat. It softens and shrinks slightly.',
|
|
230
|
+
delivery: SHIPS,
|
|
231
|
+
story: [
|
|
232
|
+
'The first thing we ever made, and still the thing we sell most of. The body is one piece of canvas folded at the base, so there is no seam along the bottom to split, and the handles run down both sides to carry the weight.',
|
|
233
|
+
'It arrives undyed and a little stiff. After a few months it creases where you hold it and takes the shape of what you carry.',
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: 'Brushed cotton overshirt',
|
|
238
|
+
slug: 'brushed-cotton-overshirt',
|
|
239
|
+
category: 'Wear',
|
|
240
|
+
price: 98,
|
|
241
|
+
inStock: false,
|
|
242
|
+
description: 'A grey overshirt in brushed cotton flannel with a button-down collar and one chest pocket. Heavy enough to wear as a light jacket in spring.',
|
|
243
|
+
material: 'Cotton flannel, 220 g/m², brushed on both sides. Mother-of-pearl buttons.',
|
|
244
|
+
dimensions: 'Sizes S to XL. Size M: 76 cm back length, 57 cm across the chest.',
|
|
245
|
+
origin: 'Guimarães, Portugal',
|
|
246
|
+
care: 'Wash at 30 °C and line dry. Iron on low heat.',
|
|
247
|
+
delivery: 'The next batch is being cut and should reach the shop in March.',
|
|
248
|
+
story: [
|
|
249
|
+
'The flannel is brushed on both faces at a finishing mill outside Guimarães, which gives it a soft, slightly dusty surface and traps enough air to feel warm on a cold morning. We make it in one grey, a mix of undyed and black cotton.',
|
|
250
|
+
'This batch sold out in six weeks. Write to us and we will tell you when the next one arrives. Nothing is charged until it does.',
|
|
251
|
+
],
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: 'Cast-iron skillet',
|
|
255
|
+
slug: 'cast-iron-skillet',
|
|
256
|
+
category: 'Kitchen',
|
|
257
|
+
price: 89,
|
|
258
|
+
inStock: true,
|
|
259
|
+
description: 'A 26 cm skillet cast in one piece, with a pouring lip on each side. Seasoned with linseed oil and ready to cook with.',
|
|
260
|
+
material: 'Cast iron, seasoned with three coats of linseed oil.',
|
|
261
|
+
dimensions: '26 cm across, 5 cm deep, 45 cm with the handle.',
|
|
262
|
+
weight: '2.6 kg',
|
|
263
|
+
origin: 'Aveiro, Portugal',
|
|
264
|
+
care: 'Rinse with hot water and a brush, dry on the hob, wipe with a drop of oil. Keep it out of the dishwasher.',
|
|
265
|
+
delivery: 'Ships from Lisbon within two working days by tracked courier. Free returns within 30 days.',
|
|
266
|
+
story: [
|
|
267
|
+
'Cast in sand moulds at a foundry near Aveiro that otherwise makes parts for water pumps. The cooking surface is machined smoother than most cast iron, so the seasoning builds quickly and eggs release after a few weeks of use.',
|
|
268
|
+
'It works on gas, electric and induction hobs, in the oven and over a fire. A skillet that has rusted or lost its seasoning can be saved: we strip and reseason them at the shop for the price of the oil.',
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: 'Leather card holder',
|
|
273
|
+
slug: 'leather-card-holder',
|
|
274
|
+
category: 'Carry',
|
|
275
|
+
price: 55,
|
|
276
|
+
inStock: true,
|
|
277
|
+
description: 'A slim holder for four to six cards in vegetable-tanned leather, stitched with waxed thread. It darkens and shines where your hand holds it.',
|
|
278
|
+
material: 'Vegetable-tanned cowhide, 1.4 mm. Waxed linen thread.',
|
|
279
|
+
dimensions: '10.5 × 7.5 cm. Two outer pockets and a centre slot.',
|
|
280
|
+
origin: 'Lisbon, Portugal',
|
|
281
|
+
care: 'Keep it dry. A little neutral leather balm once a year.',
|
|
282
|
+
delivery: SHIPS,
|
|
283
|
+
story: [
|
|
284
|
+
'Cut from the offcuts of our bag handles, so none of the hide is wasted. Each holder is saddle stitched by hand at the workshop with two needles and one waxed thread, a stitch that holds even if a single loop is cut.',
|
|
285
|
+
'The leather is tanned with tree bark over several weeks, which leaves it firm and pale at first. Within a few months it takes on the colour of your pocket.',
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: 'Heavyweight T-shirt',
|
|
290
|
+
slug: 'heavyweight-t-shirt',
|
|
291
|
+
category: 'Wear',
|
|
292
|
+
price: 38,
|
|
293
|
+
inStock: true,
|
|
294
|
+
description: 'A white T-shirt in dense organic cotton jersey with a narrow ribbed collar that keeps its shape. Straight cut, slightly longer at the back.',
|
|
295
|
+
material: 'Organic cotton jersey, 240 g/m².',
|
|
296
|
+
dimensions: 'Sizes XS to XXL. Size M: 71 cm back length, 54 cm across the chest.',
|
|
297
|
+
origin: 'Barcelos, Portugal',
|
|
298
|
+
care: 'Wash at 40 °C and dry flat or on a line. Allow 3% shrinkage on the first wash.',
|
|
299
|
+
delivery: SHIPS,
|
|
300
|
+
story: [
|
|
301
|
+
'Knitted and sewn by a family firm in Barcelos that has made T-shirts for other labels for three generations. The jersey is heavier than most, which keeps a white T-shirt from turning sheer and lets the collar lie flat after hundreds of washes.',
|
|
302
|
+
'We make it in white only, because white is the one we wear. The body is cut straight, so take a size up for a looser fit.',
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: 'Striped wool blanket',
|
|
307
|
+
slug: 'striped-wool-blanket',
|
|
308
|
+
category: 'Living',
|
|
309
|
+
price: 190,
|
|
310
|
+
inStock: true,
|
|
311
|
+
description: 'A heavy blanket in undyed wool with indigo stripes and hand-knotted fringes. Large enough for the foot of a double bed or the back of a sofa.',
|
|
312
|
+
material: 'Serra da Estrela wool, undyed, with indigo-dyed stripes.',
|
|
313
|
+
dimensions: '130 × 190 cm, plus 8 cm of fringe at each end.',
|
|
314
|
+
weight: '1.6 kg',
|
|
315
|
+
origin: 'Manteigas, Portugal',
|
|
316
|
+
care: 'Air it outside rather than washing it. Hand wash cold with wool soap if needed and dry flat.',
|
|
317
|
+
delivery: SHIPS,
|
|
318
|
+
story: [
|
|
319
|
+
'Woven on a shuttle loom in Manteigas, a mountain town where wool has been washed in the river Zêzere for five hundred years. The mill buys fleece from shepherds in the Serra da Estrela and spins it without bleaching, so the cream of the blanket is the colour of the sheep.',
|
|
320
|
+
'The stripes are dyed with indigo in small lots, and the fringes are knotted by hand as each blanket comes off the loom. Wool this dense keeps out a draught and seldom needs washing: a dry day on a line is enough.',
|
|
321
|
+
],
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: 'Porcelain pour-over set',
|
|
325
|
+
slug: 'porcelain-pour-over-set',
|
|
326
|
+
category: 'Kitchen',
|
|
327
|
+
price: 72,
|
|
328
|
+
inStock: true,
|
|
329
|
+
description: 'A porcelain dripper and a 600 ml glass carafe with a cork collar, for two large cups at a time. The dripper takes size 2 paper filters.',
|
|
330
|
+
material: 'Porcelain, borosilicate glass, natural cork.',
|
|
331
|
+
capacity: '600 ml, two large cups.',
|
|
332
|
+
dimensions: 'Carafe 19 cm high. Dripper 11.5 cm across.',
|
|
333
|
+
origin: 'Porcelain from Ílhavo, Portugal. Glass from Bohemia, Czech Republic.',
|
|
334
|
+
care: 'Dishwasher safe once the cork collar is lifted off. Wipe the cork dry.',
|
|
335
|
+
delivery: 'Ships from Lisbon within two working days, packed in recycled paper. Free returns within 30 days.',
|
|
336
|
+
story: [
|
|
337
|
+
'The dripper is made at a porcelain works in Ílhavo that has supplied restaurants in Porto since the 1950s. Its ribs are cut deep enough that the paper never seals against the wall, so the water runs through evenly and a pour takes about three minutes.',
|
|
338
|
+
'The carafe is mouth-blown borosilicate glass and can sit on a gas flame at low heat to keep coffee warm. The cork collar lets you hold it by the neck without a cloth.',
|
|
339
|
+
],
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: 'Canvas shoulder bag',
|
|
343
|
+
slug: 'canvas-shoulder-bag',
|
|
344
|
+
category: 'Carry',
|
|
345
|
+
price: 165,
|
|
346
|
+
inStock: true,
|
|
347
|
+
description: 'A roomy bag in tan cotton canvas with bridle leather handles and a detachable strap. Wide enough for a laptop and a change of clothes.',
|
|
348
|
+
material: 'Cotton canvas, 18 oz. Vegetable-tanned bridle leather. Steel hooks.',
|
|
349
|
+
dimensions: '40 × 34 × 14 cm. The strap adjusts from 90 to 130 cm.',
|
|
350
|
+
weight: '1.1 kg',
|
|
351
|
+
origin: 'Lisbon, Portugal',
|
|
352
|
+
care: 'Brush off dust and spot clean with cold water. Condition the leather once a year.',
|
|
353
|
+
delivery: SHIPS,
|
|
354
|
+
story: [
|
|
355
|
+
'We cut and sew this bag in our own workshop behind the shop, twelve at a time. The canvas is a heavy cotton duck that holds its shape when the bag is empty and relaxes as it fills, and each handle is one length of bridle leather riveted through both layers.',
|
|
356
|
+
'Every seam that carries weight is stitched twice. The strap clips off with two steel hooks, so the bag goes by hand on a train and over the shoulder on a bicycle. We keep spare straps and hooks at the workshop for as long as we make the bag.',
|
|
357
|
+
],
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
name: 'Field jacket',
|
|
361
|
+
slug: 'field-jacket',
|
|
362
|
+
category: 'Wear',
|
|
363
|
+
price: 245,
|
|
364
|
+
inStock: true,
|
|
365
|
+
description: 'A four-pocket jacket in olive cotton drill, cut long enough to cover a sweater and loose enough to wear over one. Corozo buttons and a stand collar.',
|
|
366
|
+
material: 'Cotton drill, 280 g/m², garment washed. Corozo nut buttons.',
|
|
367
|
+
dimensions: 'Sizes XS to XL. Size M: 74 cm back length, 58 cm across the chest.',
|
|
368
|
+
origin: 'Guimarães, Portugal',
|
|
369
|
+
care: 'Wash at 30 °C inside out, line dry, iron warm.',
|
|
370
|
+
delivery: SHIPS,
|
|
371
|
+
story: [
|
|
372
|
+
'The pattern comes from a work jacket that one of our tailors in Guimarães wore for twenty years. We kept its proportions, raised the chest pockets two centimetres so they clear a bag strap, and chose corozo buttons, which survive a hot wash and can be sewn back on at the shop.',
|
|
373
|
+
'The drill is woven for us in runs of four hundred metres and washed once before cutting, so the jacket will not shrink after it reaches you. It softens and fades along the seams over a few years of wear. When a pocket tears or a cuff frays, send it back and we will mend it.',
|
|
374
|
+
],
|
|
375
|
+
},
|
|
376
|
+
];
|
|
377
|
+
export const STORE_DEMO_CATEGORIES = [
|
|
378
|
+
{
|
|
379
|
+
name: 'Wear',
|
|
380
|
+
slug: 'wear',
|
|
381
|
+
summary: 'Jackets, shirts and knitwear cut in Guimarães, Barcelos and Covilhã, and mended at the shop when they tear.',
|
|
382
|
+
photoOf: 'cable-knit-beanie',
|
|
383
|
+
relatedTitle: 'More to wear',
|
|
384
|
+
makers: [
|
|
385
|
+
'Our clothes come from three workshops in the north of Portugal, each of which has sewn or knitted for larger labels for decades. We choose cloth with them, agree a pattern, and order in runs small enough to sell within a season.',
|
|
386
|
+
'Every garment is sold with the promise we make for everything in the shop: if a seam opens, a pocket tears or a button goes, bring it or send it back and we will repair it for as long as we sell the piece.',
|
|
387
|
+
],
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
name: 'Carry',
|
|
391
|
+
slug: 'carry',
|
|
392
|
+
summary: 'Bags and small leather goods, most of them cut and sewn at our own workshop behind the shop in Lisbon.',
|
|
393
|
+
photoOf: 'leather-card-holder',
|
|
394
|
+
relatedTitle: 'More to carry',
|
|
395
|
+
makers: [
|
|
396
|
+
'Canvas comes from a mill in Guimarães and leather from a vegetable tannery in Alcanena. Both are cut on the long table at the back of the shop, and anyone buying a bag can watch the next one being sewn.',
|
|
397
|
+
'We keep spare straps, hooks and rivets for everything we have made, so a bag can be put right long after the batch it came from has sold out.',
|
|
398
|
+
],
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: 'Kitchen',
|
|
402
|
+
slug: 'kitchen',
|
|
403
|
+
summary: 'Cookware and tableware from a foundry, a porcelain works and an enamel maker, for use every day.',
|
|
404
|
+
photoOf: 'cast-iron-skillet',
|
|
405
|
+
relatedTitle: 'More for the kitchen',
|
|
406
|
+
makers: [
|
|
407
|
+
'Nothing in the kitchen range is coated, and nothing needs to be handled gently. Cast iron, porcelain, glass and enamelled steel all last for decades when they are used every day, which is the only way we test them.',
|
|
408
|
+
'We cook with every piece at the shop before we sell it, and we reseason skillets for customers for the price of the oil.',
|
|
409
|
+
],
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: 'Living',
|
|
413
|
+
slug: 'living',
|
|
414
|
+
summary: 'Blankets and table linen woven on old looms in Manteigas and Guimarães, from wool and flax left undyed.',
|
|
415
|
+
photoOf: 'linen-table-runner',
|
|
416
|
+
relatedTitle: 'More for the house',
|
|
417
|
+
makers: [
|
|
418
|
+
'Our blankets and linens come from two weaving mills that still run shuttle looms, slower than modern machines and able to weave a denser cloth with a finished edge.',
|
|
419
|
+
'The mills weave for us twice a year, in spring and in autumn, so a piece that sells out comes back with the next season.',
|
|
420
|
+
],
|
|
421
|
+
},
|
|
422
|
+
];
|
|
423
|
+
function productsGrid(key, options) {
|
|
424
|
+
return {
|
|
425
|
+
_key: key,
|
|
426
|
+
_type: 'collectionList',
|
|
427
|
+
_version: BLOCK_VERSION,
|
|
428
|
+
...(options.title === undefined ? {} : { title: options.title }),
|
|
429
|
+
collection: 'product',
|
|
430
|
+
...(options.category === undefined ? {} : { filter: { category: options.category } }),
|
|
431
|
+
sort: { field: 'createdAt', direction: 'desc' },
|
|
432
|
+
limit: options.limit,
|
|
433
|
+
layout: 'grid',
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
const COMMITMENTS = {
|
|
437
|
+
_key: 'commitments',
|
|
438
|
+
_type: 'featureGrid',
|
|
439
|
+
_version: BLOCK_VERSION,
|
|
440
|
+
items: [
|
|
441
|
+
{
|
|
442
|
+
_key: 'commitment-delivery',
|
|
443
|
+
title: 'Delivery across the EU in 2 to 5 working days',
|
|
444
|
+
link: { href: '/delivery-and-returns' },
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
_key: 'commitment-returns',
|
|
448
|
+
title: '30 days to return anything unworn',
|
|
449
|
+
link: { href: '/delivery-and-returns' },
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
_key: 'commitment-repairs',
|
|
453
|
+
title: 'Repairs for as long as we sell the piece',
|
|
454
|
+
link: { href: '/repairs' },
|
|
455
|
+
},
|
|
456
|
+
],
|
|
457
|
+
};
|
|
458
|
+
function withKey(block, key) {
|
|
459
|
+
return { ...block, _key: key };
|
|
460
|
+
}
|
|
461
|
+
/** The body of one product's page: how it is made, then more from its category. */
|
|
462
|
+
export function storeProductBlocks(demo) {
|
|
463
|
+
const group = STORE_DEMO_CATEGORIES.find((candidate) => candidate.name === demo.category);
|
|
464
|
+
return [
|
|
465
|
+
prose(`product-${demo.slug}-story`, [...demo.story]),
|
|
466
|
+
productsGrid(`product-${demo.slug}-more`, {
|
|
467
|
+
title: group?.relatedTitle ?? 'More from the shop',
|
|
468
|
+
category: demo.category,
|
|
469
|
+
limit: 5,
|
|
470
|
+
}),
|
|
471
|
+
];
|
|
472
|
+
}
|
|
473
|
+
/** The body of a category's page: its goods, then the people who make them. */
|
|
474
|
+
export function storeCategoryBlocks(demo) {
|
|
475
|
+
return [
|
|
476
|
+
productsGrid(`category-${demo.slug}-goods`, { category: demo.name, limit: 24 }),
|
|
477
|
+
prose(`category-${demo.slug}-makers`, [...demo.makers]),
|
|
478
|
+
];
|
|
479
|
+
}
|
|
480
|
+
export function buildStoreHomeBlocks(options = {}) {
|
|
481
|
+
const media = options.media ?? {};
|
|
482
|
+
const siteName = nameOf(options.siteName);
|
|
483
|
+
const letters = shopEmail(siteName, 'letters');
|
|
484
|
+
return [
|
|
485
|
+
{
|
|
486
|
+
_key: 'home-banner',
|
|
487
|
+
_type: 'hero',
|
|
488
|
+
_version: BLOCK_VERSION,
|
|
489
|
+
title: 'Things for every day, made to last and to be mended',
|
|
490
|
+
subtitle: 'Clothing, bags and kitchenware from eleven small workshops in Portugal and beyond, sold from our shop in Lisbon and sent across Europe.',
|
|
491
|
+
...(media.hero === undefined ? {} : { media: media.hero }),
|
|
492
|
+
actions: [{ label: 'Shop the collection', target: { href: '/shop' }, emphasis: 'primary' }],
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
_key: 'home-categories',
|
|
496
|
+
_type: 'collectionList',
|
|
497
|
+
_version: BLOCK_VERSION,
|
|
498
|
+
collection: 'category',
|
|
499
|
+
sort: { field: 'createdAt', direction: 'asc' },
|
|
500
|
+
limit: 4,
|
|
501
|
+
layout: 'grid',
|
|
502
|
+
},
|
|
503
|
+
productsGrid('home-new', { title: 'New this season', limit: 4 }),
|
|
504
|
+
withKey(COMMITMENTS, 'home-commitments'),
|
|
505
|
+
...(media.story === undefined
|
|
506
|
+
? []
|
|
507
|
+
: [
|
|
508
|
+
{
|
|
509
|
+
_key: 'home-story',
|
|
510
|
+
_type: 'mediaFigure',
|
|
511
|
+
_version: BLOCK_VERSION,
|
|
512
|
+
media: media.story,
|
|
513
|
+
ratio: '1:1',
|
|
514
|
+
align: 'start',
|
|
515
|
+
caption: `${siteName} opened in 2014 as a repair bench at the back of a haberdashery on Rua da Boavista. We still mend what we sell, and we only sell what our workshops make to be mended.`,
|
|
516
|
+
credit: 'Enamel mug, made in Olkusz',
|
|
517
|
+
},
|
|
518
|
+
]),
|
|
519
|
+
{
|
|
520
|
+
_key: 'home-letter',
|
|
521
|
+
_type: 'testimonial',
|
|
522
|
+
_version: BLOCK_VERSION,
|
|
523
|
+
quote: answer('home-letter-quote', 'I bought the field jacket in 2016 and sent it back last winter with a torn pocket and two buttons missing. It came home three weeks later with both put right and a note saying who had sewn them.'),
|
|
524
|
+
attribution: { name: 'Helena Duarte', role: 'Field jacket, bought in 2016' },
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
_key: 'home-faq',
|
|
528
|
+
_type: 'faq',
|
|
529
|
+
_version: BLOCK_VERSION,
|
|
530
|
+
title: 'Before you order',
|
|
531
|
+
items: questions('home-faq', [
|
|
532
|
+
[
|
|
533
|
+
'How do I place an order?',
|
|
534
|
+
'Use the order button on a product page to write to us, or come to the shop. We reply within one working day with the total, delivery included, and a secure payment link. Nothing is charged until you confirm.',
|
|
535
|
+
],
|
|
536
|
+
[
|
|
537
|
+
'How long does delivery take?',
|
|
538
|
+
'Two to three working days in mainland Portugal and three to five across the rest of the European Union. Orders confirmed before noon leave Lisbon the same day.',
|
|
539
|
+
],
|
|
540
|
+
[
|
|
541
|
+
'Can I collect my order from the shop?',
|
|
542
|
+
'Yes. Ask for collection when you write, and your order will be waiting at Rua da Boavista 84 from the next day, Tuesday to Saturday.',
|
|
543
|
+
],
|
|
544
|
+
[
|
|
545
|
+
'What if something does not fit?',
|
|
546
|
+
'Send it back unworn within 30 days for a refund or another size. Returns are free within Portugal, and from elsewhere in the EU we refund the cost of the cheapest tracked service.',
|
|
547
|
+
],
|
|
548
|
+
]),
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
_key: 'home-letters',
|
|
552
|
+
_type: 'cta',
|
|
553
|
+
_version: BLOCK_VERSION,
|
|
554
|
+
title: 'Letters from the workshop',
|
|
555
|
+
text: 'Four letters a year about new batches, repairs and the people who make our goods. Write to us and we will add you to the list.',
|
|
556
|
+
actions: [
|
|
557
|
+
{
|
|
558
|
+
label: 'Ask for the letters',
|
|
559
|
+
target: {
|
|
560
|
+
href: `mailto:${letters}?subject=${encodeURIComponent('Letters from the workshop')}`,
|
|
561
|
+
},
|
|
562
|
+
emphasis: 'secondary',
|
|
563
|
+
},
|
|
564
|
+
],
|
|
565
|
+
},
|
|
566
|
+
];
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* `home`, `shop` (every product), `about`, `how-to-order`,
|
|
570
|
+
* `delivery-and-returns`, `repairs`, `contact` and `terms`: every page the
|
|
571
|
+
* menus and the copy link to.
|
|
572
|
+
*/
|
|
573
|
+
export function buildStoreDemoPages(options = {}) {
|
|
574
|
+
const media = options.media ?? {};
|
|
575
|
+
const siteName = nameOf(options.siteName);
|
|
576
|
+
const hello = shopEmail(siteName);
|
|
577
|
+
const orders = shopEmail(siteName, 'orders');
|
|
578
|
+
const repairs = shopEmail(siteName, 'repairs');
|
|
579
|
+
const letters = shopEmail(siteName, 'letters');
|
|
580
|
+
return [
|
|
581
|
+
{ title: 'Home', slug: 'home', blocks: buildStoreHomeBlocks({ siteName, media }) },
|
|
582
|
+
{
|
|
583
|
+
title: 'Shop',
|
|
584
|
+
slug: 'shop',
|
|
585
|
+
blocks: [productsGrid('shop-all', { limit: 48 }), withKey(COMMITMENTS, 'shop-commitments')],
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
title: 'About',
|
|
589
|
+
slug: 'about',
|
|
590
|
+
blocks: [
|
|
591
|
+
prose('about-intro', [
|
|
592
|
+
`${siteName} began in 2014 as a repair bench at the back of a haberdashery on Rua da Boavista in Lisbon. People brought in coats with broken zips and bags with torn straps, and after a year of mending other people's things we started to make our own, built so that they could be mended too.`,
|
|
593
|
+
'Today the shop sells about sixty pieces from eleven workshops. Most of them are in the north of Portugal, within a day of each other by train: a tailoring firm in Guimarães, a knitter in Covilhã, a weaving mill in Manteigas, a foundry near Aveiro. We visit each of them at least twice a year, and we buy in runs small enough to sell through before the next season.',
|
|
594
|
+
'## How we choose what to sell',
|
|
595
|
+
'A piece comes into the shop when we have used it long enough to know how it wears, and when the person who made it can supply the parts to repair it. That rules out most things with glued soles, coated pans and zips we cannot replace. It also means the range changes slowly and some pieces sell out for months at a time.',
|
|
596
|
+
'We pay our workshops before the goods are delivered and publish the price we paid on request. Nothing is made in a quantity we cannot sell, so nothing is ever put on sale to clear a warehouse.',
|
|
597
|
+
]),
|
|
598
|
+
...(media.hero === undefined
|
|
599
|
+
? []
|
|
600
|
+
: [
|
|
601
|
+
{
|
|
602
|
+
_key: 'about-figure',
|
|
603
|
+
_type: 'mediaFigure',
|
|
604
|
+
_version: BLOCK_VERSION,
|
|
605
|
+
media: media.hero,
|
|
606
|
+
ratio: '21:9',
|
|
607
|
+
align: 'wide',
|
|
608
|
+
caption: 'A canvas tote, a lambswool beanie and a card holder, all made within a day of Lisbon.',
|
|
609
|
+
},
|
|
610
|
+
]),
|
|
611
|
+
{
|
|
612
|
+
_key: 'about-figures',
|
|
613
|
+
_type: 'stats',
|
|
614
|
+
_version: BLOCK_VERSION,
|
|
615
|
+
title: 'The shop in numbers',
|
|
616
|
+
items: [
|
|
617
|
+
{
|
|
618
|
+
_key: 'about-figure-workshops',
|
|
619
|
+
value: '11',
|
|
620
|
+
label: 'workshops we buy from, nine of them in Portugal',
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
_key: 'about-figure-repairs',
|
|
624
|
+
value: '1,380',
|
|
625
|
+
label: 'repairs done at the bench last year',
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
_key: 'about-figure-opened',
|
|
629
|
+
value: '2014',
|
|
630
|
+
label: 'the year we opened on Rua da Boavista',
|
|
631
|
+
},
|
|
632
|
+
],
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
_key: 'about-visit',
|
|
636
|
+
_type: 'cta',
|
|
637
|
+
_version: BLOCK_VERSION,
|
|
638
|
+
title: 'Visit the shop and the repair bench',
|
|
639
|
+
text: 'Rua da Boavista 84, Lisbon. Open Tuesday to Saturday, 11:00 to 19:00.',
|
|
640
|
+
actions: [
|
|
641
|
+
{
|
|
642
|
+
label: 'Opening hours and directions',
|
|
643
|
+
target: { href: '/contact' },
|
|
644
|
+
emphasis: 'secondary',
|
|
645
|
+
},
|
|
646
|
+
],
|
|
647
|
+
},
|
|
648
|
+
],
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
title: 'How to order',
|
|
652
|
+
slug: 'how-to-order',
|
|
653
|
+
blocks: [
|
|
654
|
+
prose('order-intro', [
|
|
655
|
+
'We sell through the shop and by email, so that every order is read by the person who will pack it. It takes a little longer than a checkout and it means we can answer a question about size or delivery before you pay.',
|
|
656
|
+
'## Ordering by email',
|
|
657
|
+
`Use the order button on any product page, or write to [${orders}](mailto:${orders}) with the pieces, the sizes and the delivery address. We reply within one working day, Tuesday to Saturday, with the total including delivery and a secure payment link by card or MB WAY. Your order leaves Lisbon as soon as the payment arrives.`,
|
|
658
|
+
'## Ordering at the shop',
|
|
659
|
+
'Everything on the site is in the shop at Rua da Boavista 84, and most sizes can be tried on. We can also keep an item aside for a week if you tell us when you plan to come.',
|
|
660
|
+
]),
|
|
661
|
+
{
|
|
662
|
+
_key: 'order-notes',
|
|
663
|
+
_type: 'accordion',
|
|
664
|
+
_version: BLOCK_VERSION,
|
|
665
|
+
title: 'Payment and delivery',
|
|
666
|
+
items: questions('order-notes', [
|
|
667
|
+
[
|
|
668
|
+
'Which payment methods do you accept?',
|
|
669
|
+
'Visa, Mastercard and MB WAY through a payment link, bank transfer to our account in Lisbon, and cash or card at the shop. Prices include Portuguese VAT.',
|
|
670
|
+
],
|
|
671
|
+
[
|
|
672
|
+
'What does delivery cost?',
|
|
673
|
+
'Delivery is €5 in mainland Portugal and €12 to the rest of the EU, free on orders over €150. Blankets and skillets travel by tracked courier at the same price.',
|
|
674
|
+
],
|
|
675
|
+
[
|
|
676
|
+
'Do you send orders outside the European Union?',
|
|
677
|
+
'We send to the United Kingdom, Switzerland and Norway at cost. Import duties and taxes are paid by the recipient on arrival, and we tell you the estimate before you pay.',
|
|
678
|
+
],
|
|
679
|
+
[
|
|
680
|
+
'Can you wrap a gift?',
|
|
681
|
+
'Yes, in brown paper and cotton string, with a card written by hand if you send us the words. It costs nothing.',
|
|
682
|
+
],
|
|
683
|
+
]),
|
|
684
|
+
},
|
|
685
|
+
],
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
title: 'Delivery and returns',
|
|
689
|
+
slug: 'delivery-and-returns',
|
|
690
|
+
blocks: [
|
|
691
|
+
prose('delivery-intro', [
|
|
692
|
+
'Orders leave the shop in Lisbon every working day at 14:00, packed in recycled paper and card with no plastic. You receive a tracking number by email when the parcel is collected.',
|
|
693
|
+
]),
|
|
694
|
+
{
|
|
695
|
+
_key: 'delivery-faq',
|
|
696
|
+
_type: 'faq',
|
|
697
|
+
_version: BLOCK_VERSION,
|
|
698
|
+
title: 'Delivery and returns',
|
|
699
|
+
items: questions('delivery-faq', [
|
|
700
|
+
[
|
|
701
|
+
'How long does delivery take?',
|
|
702
|
+
'Two to three working days in mainland Portugal, three to five working days to the rest of the European Union, and five to eight to the United Kingdom, Switzerland and Norway.',
|
|
703
|
+
],
|
|
704
|
+
[
|
|
705
|
+
'How do I return something?',
|
|
706
|
+
`Write to [${orders}](mailto:${orders}) within 30 days of delivery with your order number. We send a prepaid label for returns within Portugal, and an address for returns from elsewhere.`,
|
|
707
|
+
],
|
|
708
|
+
[
|
|
709
|
+
'When will I be refunded?',
|
|
710
|
+
'Within five working days of the parcel reaching us, to the card or account you paid from. Delivery costs are refunded when the whole order is returned.',
|
|
711
|
+
],
|
|
712
|
+
[
|
|
713
|
+
'Can I exchange a size?',
|
|
714
|
+
'Yes. Tell us the size you need when you write, and we send it as soon as the first piece is on its way back to us.',
|
|
715
|
+
],
|
|
716
|
+
[
|
|
717
|
+
'What cannot be returned?',
|
|
718
|
+
'Pieces that have been worn, washed or altered, and anything we have repaired or made to order for you.',
|
|
719
|
+
],
|
|
720
|
+
]),
|
|
721
|
+
},
|
|
722
|
+
],
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
title: 'Repairs',
|
|
726
|
+
slug: 'repairs',
|
|
727
|
+
blocks: [
|
|
728
|
+
prose('repairs-intro', [
|
|
729
|
+
'We repair everything we sell for as long as we sell it, and most things long after. The bench is at the back of the shop, and two of us work at it every afternoon.',
|
|
730
|
+
'## What we mend',
|
|
731
|
+
'Seams, pockets, cuffs and buttons on clothing. Straps, hooks, rivets and stitching on bags and leather. Rust and lost seasoning on cast iron. Moth holes in knitwear, if they are small enough to darn.',
|
|
732
|
+
'## What it costs',
|
|
733
|
+
'Repairs to anything bought from us in the last two years are free. After that we charge for the materials and nothing for the time, which comes to between €5 and €25 for most repairs. We tell you the price before we start.',
|
|
734
|
+
'## How to send something in',
|
|
735
|
+
`Bring it to the shop, or write to [${repairs}](mailto:${repairs}) with a photograph of the damage. We reply with the price, the time it will take (usually two to three weeks) and the address to post it to.`,
|
|
736
|
+
]),
|
|
737
|
+
{
|
|
738
|
+
_key: 'repairs-figures',
|
|
739
|
+
_type: 'statCounter',
|
|
740
|
+
_version: BLOCK_VERSION,
|
|
741
|
+
title: 'Last year at the bench',
|
|
742
|
+
stats: [
|
|
743
|
+
{ _key: 'repairs-figure-total', value: '1,380', label: 'repairs completed' },
|
|
744
|
+
{ _key: 'repairs-figure-free', value: '62%', label: 'done free of charge' },
|
|
745
|
+
{
|
|
746
|
+
_key: 'repairs-figure-days',
|
|
747
|
+
value: '16',
|
|
748
|
+
label: 'days on average from arrival to return',
|
|
749
|
+
},
|
|
750
|
+
],
|
|
751
|
+
},
|
|
752
|
+
],
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
title: 'Contact',
|
|
756
|
+
slug: 'contact',
|
|
757
|
+
blocks: [
|
|
758
|
+
prose('contact-details', [
|
|
759
|
+
'## The shop',
|
|
760
|
+
'Rua da Boavista 84, 1200-066 Lisboa, Portugal. Five minutes on foot from Cais do Sodré station and the 28 tram.',
|
|
761
|
+
'Tuesday to Saturday, 11:00 to 19:00. Closed on Sundays, Mondays and public holidays.',
|
|
762
|
+
'## Writing to us',
|
|
763
|
+
`For orders, [${orders}](mailto:${orders}). For repairs, [${repairs}](mailto:${repairs}). For our letters from the workshop, four a year, [${letters}](mailto:${letters}). For anything else, including workshops that would like to work with us, [${hello}](mailto:${hello}).`,
|
|
764
|
+
'Telephone +351 213 460 218, Tuesday to Saturday during opening hours.',
|
|
765
|
+
]),
|
|
766
|
+
],
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
title: 'Terms and privacy',
|
|
770
|
+
slug: 'terms',
|
|
771
|
+
blocks: [
|
|
772
|
+
prose('terms', [
|
|
773
|
+
`These terms apply to every order placed with ${siteName}, Lda., Rua da Boavista 84, 1200-066 Lisboa, registered in Portugal under NIPC 516 204 873.`,
|
|
774
|
+
'## Orders and prices',
|
|
775
|
+
'An order is confirmed when we have received payment and replied to say so. Prices are in euros and include VAT at the Portuguese rate. Delivery costs are stated before payment.',
|
|
776
|
+
'## Your right to cancel',
|
|
777
|
+
'You may cancel an order within 14 days of receiving it without giving a reason, as the law of the European Union provides, and our own returns period of 30 days extends that right. Refunds are made within 14 days of the goods reaching us.',
|
|
778
|
+
'## Guarantee',
|
|
779
|
+
'Every piece carries the legal guarantee of conformity of three years. Our repair service is offered in addition to it and does not replace it.',
|
|
780
|
+
'## Privacy',
|
|
781
|
+
`We keep your name, address and email for as long as it takes to deliver, return or repair an order, and for the ten years Portuguese tax law requires us to keep invoices. We never sell or share them except with the carrier delivering your parcel. Write to [${hello}](mailto:${hello}) to see or delete what we hold. This site sets no advertising or tracking cookies.`,
|
|
782
|
+
]),
|
|
783
|
+
],
|
|
784
|
+
},
|
|
785
|
+
];
|
|
786
|
+
}
|
|
787
|
+
// ---------------------------------------------------------------------------
|
|
788
|
+
// Media, menus, settings
|
|
789
|
+
// ---------------------------------------------------------------------------
|
|
790
|
+
/**
|
|
791
|
+
* `store`'s own starting skin, asserted present with a real check since
|
|
792
|
+
* `STARTING_SKINS` is keyed by blueprint id. Its palette only feeds the
|
|
793
|
+
* procedural fallback of each media slot below, which is never used while
|
|
794
|
+
* the photographs are bundled.
|
|
795
|
+
*/
|
|
796
|
+
function storePalette() {
|
|
797
|
+
const skin = STARTING_SKINS.store;
|
|
798
|
+
if (skin === undefined) {
|
|
799
|
+
throw new CogentaError({
|
|
800
|
+
code: 'BLUEPRINT_REGISTRY_CORRUPT',
|
|
801
|
+
message: 'STARTING_SKINS.store is missing.',
|
|
802
|
+
hint: 'The "store" entry must stay declared in starting-skins.ts for this blueprint to render its fallback art.',
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
return skin.color;
|
|
806
|
+
}
|
|
807
|
+
const PRODUCT_ALT = {
|
|
808
|
+
'linen-table-runner': 'White linen runner with fringed ends laid along a dark oak table',
|
|
809
|
+
'enamel-mug': 'Forest green enamel mug with a steel rim on a wooden table',
|
|
810
|
+
'cable-knit-beanie': 'Charcoal cable-knit lambswool beanie against a white wall',
|
|
811
|
+
'canvas-tote': 'Undyed cotton canvas tote with long handles standing on its base',
|
|
812
|
+
'brushed-cotton-overshirt': 'Grey brushed cotton overshirt with a button-down collar and chest pocket',
|
|
813
|
+
'cast-iron-skillet': 'Black cast-iron skillet with a pouring lip, seen from above at an angle',
|
|
814
|
+
'leather-card-holder': 'Tan vegetable-tanned leather card holder with hand stitching and two cards inside',
|
|
815
|
+
'heavyweight-t-shirt': 'White heavyweight cotton T-shirt laid flat on a pale grey ground',
|
|
816
|
+
'striped-wool-blanket': 'Folded cream wool blanket with navy stripes and a knotted fringe',
|
|
817
|
+
'porcelain-pour-over-set': 'Porcelain coffee dripper resting on a glass carafe with a cork collar',
|
|
818
|
+
'canvas-shoulder-bag': 'Tan canvas shoulder bag with brown leather handles and a detachable strap',
|
|
819
|
+
'field-jacket': 'Olive cotton drill field jacket with four pockets and a stand collar',
|
|
820
|
+
};
|
|
821
|
+
/**
|
|
822
|
+
* Every visual this blueprint seeds is a bundled photograph: the banner, one
|
|
823
|
+
* per product, one per category (the photograph of a product from that
|
|
824
|
+
* category that is not on the home page's grid) and the brand story's.
|
|
825
|
+
* `spec` is `seedDemoMedia`'s fallback if a file is ever missing, never the
|
|
826
|
+
* intended picture.
|
|
827
|
+
*/
|
|
828
|
+
export const STORE_MEDIA_SPECS = [
|
|
829
|
+
{
|
|
830
|
+
name: 'hero',
|
|
831
|
+
spec: heroArt(storePalette(), 'blocks', 11),
|
|
832
|
+
alt: 'A canvas tote, a knitted beanie and a leather card holder laid out on undyed linen',
|
|
833
|
+
photo: 'store/hero.jpg',
|
|
834
|
+
},
|
|
835
|
+
...STORE_DEMO_CATEGORIES.map((demo, index) => ({
|
|
836
|
+
name: `category-${demo.slug}`,
|
|
837
|
+
spec: coverArt(storePalette(), 50 + index),
|
|
838
|
+
alt: PRODUCT_ALT[demo.photoOf] ?? demo.name,
|
|
839
|
+
photo: `store/${demo.photoOf}.jpg`,
|
|
840
|
+
})),
|
|
841
|
+
{
|
|
842
|
+
name: 'story',
|
|
843
|
+
spec: coverArt(storePalette(), 60),
|
|
844
|
+
alt: PRODUCT_ALT['enamel-mug'],
|
|
845
|
+
photo: 'store/enamel-mug.jpg',
|
|
846
|
+
},
|
|
847
|
+
...STORE_DEMO_PRODUCTS.map((demo, index) => ({
|
|
848
|
+
name: `product-${demo.slug}`,
|
|
849
|
+
spec: productArt(storePalette(), index + 1),
|
|
850
|
+
alt: PRODUCT_ALT[demo.slug] ?? demo.name,
|
|
851
|
+
photo: `store/${demo.slug}.jpg`,
|
|
852
|
+
})),
|
|
853
|
+
];
|
|
854
|
+
export const STORE_MENUS = {
|
|
855
|
+
header: [
|
|
856
|
+
{ label: 'Shop', url: '/shop' },
|
|
857
|
+
...STORE_DEMO_CATEGORIES.map((demo) => ({ label: demo.name, url: `/category/${demo.slug}` })),
|
|
858
|
+
{ label: 'About', url: '/about' },
|
|
859
|
+
],
|
|
860
|
+
footer: [
|
|
861
|
+
{ label: 'About', url: '/about' },
|
|
862
|
+
{ label: 'How to order', url: '/how-to-order' },
|
|
863
|
+
{ label: 'Delivery and returns', url: '/delivery-and-returns' },
|
|
864
|
+
{ label: 'Repairs', url: '/repairs' },
|
|
865
|
+
{ label: 'Contact', url: '/contact' },
|
|
866
|
+
{ label: 'Terms and privacy', url: '/terms' },
|
|
867
|
+
],
|
|
868
|
+
headerAction: { label: 'How to order', url: '/how-to-order' },
|
|
869
|
+
};
|
|
870
|
+
const HOME = { kind: 'home' };
|
|
871
|
+
const SEARCH_RESULTS = { kind: 'search' };
|
|
872
|
+
const pagePath = (path) => ({ kind: 'path', path });
|
|
873
|
+
const CUSTOMER_CARE_PAGES = [
|
|
874
|
+
{ label: 'How to order', href: '/how-to-order' },
|
|
875
|
+
{ label: 'Delivery and returns', href: '/delivery-and-returns' },
|
|
876
|
+
{ label: 'Repairs', href: '/repairs' },
|
|
877
|
+
{ label: 'Contact', href: '/contact' },
|
|
878
|
+
{ label: 'Terms and privacy', href: '/terms' },
|
|
879
|
+
];
|
|
880
|
+
function shownOn(mode, ...targets) {
|
|
881
|
+
return { pages: { mode, targets } };
|
|
882
|
+
}
|
|
883
|
+
export const STORE_WIDGETS = [
|
|
884
|
+
{
|
|
885
|
+
area: 'sidebar',
|
|
886
|
+
type: 'recentEntries',
|
|
887
|
+
title: 'New in the shop',
|
|
888
|
+
settings: { collection: 'product', count: 4, showDate: false, showImage: true },
|
|
889
|
+
visibility: shownOn('only', SEARCH_RESULTS),
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
area: 'sidebar',
|
|
893
|
+
type: 'links',
|
|
894
|
+
title: 'Customer care',
|
|
895
|
+
settings: { items: CUSTOMER_CARE_PAGES.map((item) => ({ ...item })) },
|
|
896
|
+
visibility: shownOn('only', ...CUSTOMER_CARE_PAGES.map((item) => pagePath(item.href)), SEARCH_RESULTS),
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
area: 'sidebar',
|
|
900
|
+
type: 'cta',
|
|
901
|
+
title: 'Before you order',
|
|
902
|
+
settings: {
|
|
903
|
+
heading: 'A question about a size or a delivery',
|
|
904
|
+
body: 'Write to us or call the shop. We reply within one working day, Tuesday to Saturday, and nothing is charged until you confirm.',
|
|
905
|
+
label: 'Contact the shop',
|
|
906
|
+
href: '/contact',
|
|
907
|
+
},
|
|
908
|
+
visibility: shownOn('only', ...CUSTOMER_CARE_PAGES.filter((item) => item.href !== '/contact').map((item) => pagePath(item.href))),
|
|
909
|
+
},
|
|
910
|
+
{
|
|
911
|
+
area: 'footer-1',
|
|
912
|
+
type: 'contact',
|
|
913
|
+
title: 'Call the shop',
|
|
914
|
+
settings: {
|
|
915
|
+
phone: '+351 213 460 218',
|
|
916
|
+
hours: [{ label: 'Answered', value: 'Tuesday to Saturday, during opening hours' }],
|
|
917
|
+
},
|
|
918
|
+
visibility: shownOn('except', pagePath('/contact')),
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
area: 'footer-2',
|
|
922
|
+
type: 'cta',
|
|
923
|
+
title: 'Letters from the workshop',
|
|
924
|
+
settings: {
|
|
925
|
+
heading: 'Four letters a year',
|
|
926
|
+
body: 'New batches, repairs and the people who make our goods, written from the bench in Lisbon.',
|
|
927
|
+
label: 'How to ask for them',
|
|
928
|
+
href: '/contact',
|
|
929
|
+
},
|
|
930
|
+
visibility: shownOn('except', HOME, pagePath('/contact')),
|
|
931
|
+
},
|
|
932
|
+
];
|
|
933
|
+
export const STORE_SITE_SETTINGS = {
|
|
934
|
+
'general.tagline': 'Everyday goods, made to last and to be mended.',
|
|
935
|
+
'general.socialLinks': [
|
|
936
|
+
{ label: 'Instagram', url: 'https://instagram.com/example' },
|
|
937
|
+
{ label: 'Pinterest', url: 'https://pinterest.com/example' },
|
|
938
|
+
],
|
|
939
|
+
'general.footerNote': 'Rua da Boavista 84, 1200-066 Lisboa, Portugal. Shop and repair bench open Tuesday to Saturday, 11:00 to 19:00.\n\nRegistered in Portugal as a private limited company, NIPC 516 204 873.',
|
|
940
|
+
// A catalogue is not a discussion: no comment form under a product. An
|
|
941
|
+
// editor can open comments again per collection or per entry.
|
|
942
|
+
'discussion.enabled': false,
|
|
943
|
+
};
|
|
944
|
+
export const STORE_RECOMMENDED_AGENTS = [
|
|
945
|
+
{
|
|
946
|
+
name: 'performanceAgent',
|
|
947
|
+
package: '@cogenta/agents-builtin',
|
|
948
|
+
reason: 'Catches oversized product photography before it slows the shop grid down.',
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
name: 'seoAgent',
|
|
952
|
+
package: '@cogenta/agents-builtin',
|
|
953
|
+
reason: 'Audits product pages so each one is findable by name, not only from the grid.',
|
|
954
|
+
},
|
|
955
|
+
];
|
|
956
|
+
/** The email address a product's order button writes to, with the product in the subject line. */
|
|
957
|
+
export function orderLinkFor(siteName, productName) {
|
|
958
|
+
return `mailto:${shopEmail(nameOf(siteName), 'orders')}?subject=${encodeURIComponent(`Order: ${productName}`)}`;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Inserts the blueprint's content through the real `ContentStore`, never
|
|
962
|
+
* mocked (house rule): categories first (the home page lists them in that
|
|
963
|
+
* order), then the products oldest first, then the pages. Everything is
|
|
964
|
+
* published, so every list and every link has something real behind it.
|
|
965
|
+
*/
|
|
966
|
+
async function seedStoreDemoContent(ctx) {
|
|
967
|
+
const { db, defaultLocale, adminId, media } = ctx;
|
|
968
|
+
const siteName = nameOf(ctx.siteName);
|
|
969
|
+
const categoryStore = createContentStore({ db, collection: category, defaultLocale });
|
|
970
|
+
const productStore = createContentStore({ db, collection: product, defaultLocale });
|
|
971
|
+
const pageStore = createContentStore({ db, collection: page, defaultLocale });
|
|
972
|
+
for (const demo of STORE_DEMO_CATEGORIES) {
|
|
973
|
+
const photo = media[`category-${demo.slug}`];
|
|
974
|
+
await categoryStore.create({
|
|
975
|
+
status: 'published',
|
|
976
|
+
createdBy: adminId,
|
|
977
|
+
values: {
|
|
978
|
+
name: demo.name,
|
|
979
|
+
slug: demo.slug,
|
|
980
|
+
summary: demo.summary,
|
|
981
|
+
...(photo === undefined ? {} : { photo }),
|
|
982
|
+
},
|
|
983
|
+
blocks: { blocks: storeCategoryBlocks(demo).map(toBlockZoneEntry) },
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
for (const demo of STORE_DEMO_PRODUCTS) {
|
|
987
|
+
const photo = media[`product-${demo.slug}`];
|
|
988
|
+
await productStore.create({
|
|
989
|
+
status: 'published',
|
|
990
|
+
createdBy: adminId,
|
|
991
|
+
values: {
|
|
992
|
+
name: demo.name,
|
|
993
|
+
slug: demo.slug,
|
|
994
|
+
description: demo.description,
|
|
995
|
+
price: demo.price,
|
|
996
|
+
currency: 'EUR',
|
|
997
|
+
category: demo.category,
|
|
998
|
+
inStock: demo.inStock,
|
|
999
|
+
material: demo.material,
|
|
1000
|
+
dimensions: demo.dimensions,
|
|
1001
|
+
...(demo.weight === undefined ? {} : { weight: demo.weight }),
|
|
1002
|
+
...(demo.capacity === undefined ? {} : { capacity: demo.capacity }),
|
|
1003
|
+
origin: demo.origin,
|
|
1004
|
+
care: demo.care,
|
|
1005
|
+
delivery: demo.delivery,
|
|
1006
|
+
orderLink: orderLinkFor(siteName, demo.name),
|
|
1007
|
+
...(photo === undefined ? {} : { photo }),
|
|
1008
|
+
},
|
|
1009
|
+
blocks: { blocks: storeProductBlocks(demo).map(toBlockZoneEntry) },
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
for (const demo of buildStoreDemoPages({ siteName, media })) {
|
|
1013
|
+
await pageStore.create({
|
|
1014
|
+
status: 'published',
|
|
1015
|
+
createdBy: adminId,
|
|
1016
|
+
values: { title: demo.title, slug: demo.slug },
|
|
1017
|
+
blocks: { blocks: demo.blocks.map(toBlockZoneEntry) },
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
export const storeContentPack = {
|
|
1022
|
+
collections: STORE_COLLECTIONS,
|
|
1023
|
+
recommendedAgents: STORE_RECOMMENDED_AGENTS,
|
|
1024
|
+
seedDemoContent: seedStoreDemoContent,
|
|
1025
|
+
defaultTheme: '@cogenta/theme-ecommerce',
|
|
1026
|
+
menus: STORE_MENUS,
|
|
1027
|
+
widgets: STORE_WIDGETS,
|
|
1028
|
+
siteSettings: STORE_SITE_SETTINGS,
|
|
1029
|
+
mediaSpecs: STORE_MEDIA_SPECS,
|
|
1030
|
+
};
|
|
1031
|
+
//# sourceMappingURL=store.js.map
|