@aglyn/plugins-marketplace 1.0.0 → 1.1.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/package.json +14 -14
- package/src/lib/components/listing-detail-editor.component.js +26 -26
- package/src/lib/components/listing-detail-editor.component.js.map +1 -1
- package/src/lib/components/plugin-review-detail.component.js +17 -3
- package/src/lib/components/plugin-review-detail.component.js.map +1 -1
- package/src/lib/components/plugin-reviews-queue.component.js +88 -12
- package/src/lib/components/plugin-reviews-queue.component.js.map +1 -1
- package/src/lib/components/site-starter-review-detail.component.d.ts +115 -0
- package/src/lib/components/site-starter-review-detail.component.js +785 -0
- package/src/lib/components/site-starter-review-detail.component.js.map +1 -0
- package/src/lib/components/starter-picker-shelf.component.d.ts +7 -0
- package/src/lib/components/starter-picker-shelf.component.js +75 -1
- package/src/lib/components/starter-picker-shelf.component.js.map +1 -1
- package/src/lib/hooks/use-starter-answers.d.ts +41 -0
- package/src/lib/hooks/use-starter-answers.js +75 -0
- package/src/lib/hooks/use-starter-answers.js.map +1 -0
- package/src/lib/model/listing-query.d.ts +20 -2
- package/src/lib/model/listing-query.js +55 -0
- package/src/lib/model/listing-query.js.map +1 -1
- package/src/lib/model/plugin-rejection-categories.d.ts +17 -2
- package/src/lib/model/plugin-rejection-categories.js +48 -4
- package/src/lib/model/plugin-rejection-categories.js.map +1 -1
- package/src/lib/model/plugin-review-checklist.d.ts +38 -8
- package/src/lib/model/plugin-review-checklist.js +60 -11
- package/src/lib/model/plugin-review-checklist.js.map +1 -1
- package/src/lib/model/plugin-review-status.d.ts +8 -0
- package/src/lib/model/plugin-review-status.js +34 -0
- package/src/lib/model/plugin-review-status.js.map +1 -1
- package/src/lib/model/starter-category.d.ts +58 -0
- package/src/lib/model/starter-category.js +79 -0
- package/src/lib/model/starter-category.js.map +1 -0
- package/src/lib/model/starter-query.d.ts +27 -0
- package/src/lib/model/starter-query.js +42 -0
- package/src/lib/model/starter-query.js.map +1 -1
- package/src/lib/server/admin-reviews.js +294 -45
- package/src/lib/server/admin-reviews.js.map +1 -1
- package/src/lib/server/publish-plugin.js +46 -4
- package/src/lib/server/publish-plugin.js.map +1 -1
- package/src/lib/server/publish-starter.js +4 -0
- package/src/lib/server/publish-starter.js.map +1 -1
- package/src/lib/server/publisher-review-email.d.ts +5 -0
- package/src/lib/server/publisher-review-email.js +2 -1
- package/src/lib/server/publisher-review-email.js.map +1 -1
- package/src/lib/server/starters.d.ts +4 -1
- package/src/lib/server/starters.js +128 -28
- package/src/lib/server/starters.js.map +1 -1
- package/src/lib/server.js +12 -1
- package/src/lib/server.js.map +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* What a site starter is filed under (AGL-4049): the kind of site it is, by
|
|
19
|
+
* the style names a person picks a style by ("Home services & trades"). The
|
|
20
|
+
* platform's seed files each kind's starters under that name, and the starter
|
|
21
|
+
* shelf groups by it.
|
|
22
|
+
*
|
|
23
|
+
* A starter keeps its category in its own `category` field, as its publish
|
|
24
|
+
* writes it — never the plugin taxonomy's `categories` list, whose fixed
|
|
25
|
+
* values ("analytics", "seo", …) describe what a plugin does, not a site.
|
|
26
|
+
*
|
|
27
|
+
* Pure data and readers, safe for any surface to import.
|
|
28
|
+
*/
|
|
29
|
+
/** The kinds of site a starter can be filed under, in the order a person sees the styles. */
|
|
30
|
+
export declare const SITE_STARTER_CATEGORIES: readonly string[];
|
|
31
|
+
/** The most characters a category holds, as the starter publish caps it. */
|
|
32
|
+
export declare const SITE_STARTER_CATEGORY_MAX = 40;
|
|
33
|
+
/**
|
|
34
|
+
* The Category menu for a starter: every kind of site, and the category it
|
|
35
|
+
* already has when that is not one of them — a publisher's own words, or an
|
|
36
|
+
* older listing's — so the menu never opens on a value it does not offer.
|
|
37
|
+
*/
|
|
38
|
+
export declare function siteStarterCategoryOptions(current: unknown): string[];
|
|
39
|
+
/** What a starter's listing edit does to its category. */
|
|
40
|
+
export type SiteStarterCategoryUpdate =
|
|
41
|
+
/** `undefined`: the save names no category; `null`: it clears it. */
|
|
42
|
+
{
|
|
43
|
+
ok: true;
|
|
44
|
+
category: string | null | undefined;
|
|
45
|
+
} | {
|
|
46
|
+
ok: false;
|
|
47
|
+
error: string;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* A listing edit's category for a starter, from `category`, or from the first
|
|
51
|
+
* of `categories` as the native listing editors send it. A kind of site is
|
|
52
|
+
* taken, and so is the category the starter already has; an empty value
|
|
53
|
+
* clears it. Anything else is refused in the words the editor's field uses.
|
|
54
|
+
*/
|
|
55
|
+
export declare function siteStarterCategoryUpdate(body: {
|
|
56
|
+
category?: unknown;
|
|
57
|
+
categories?: unknown;
|
|
58
|
+
}, current: unknown): SiteStarterCategoryUpdate;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { SITE_STYLES } from "@aglyn/aglyn/app-utils/site-styles";
|
|
17
|
+
/**
|
|
18
|
+
* What a site starter is filed under (AGL-4049): the kind of site it is, by
|
|
19
|
+
* the style names a person picks a style by ("Home services & trades"). The
|
|
20
|
+
* platform's seed files each kind's starters under that name, and the starter
|
|
21
|
+
* shelf groups by it.
|
|
22
|
+
*
|
|
23
|
+
* A starter keeps its category in its own `category` field, as its publish
|
|
24
|
+
* writes it — never the plugin taxonomy's `categories` list, whose fixed
|
|
25
|
+
* values ("analytics", "seo", …) describe what a plugin does, not a site.
|
|
26
|
+
*
|
|
27
|
+
* Pure data and readers, safe for any surface to import.
|
|
28
|
+
*/ /** The kinds of site a starter can be filed under, in the order a person sees the styles. */ export const SITE_STARTER_CATEGORIES = SITE_STYLES.map((style)=>style.name);
|
|
29
|
+
/** The most characters a category holds, as the starter publish caps it. */ export const SITE_STARTER_CATEGORY_MAX = 40;
|
|
30
|
+
const currentOf = (current)=>typeof current === 'string' ? current.trim() : '';
|
|
31
|
+
/**
|
|
32
|
+
* The Category menu for a starter: every kind of site, and the category it
|
|
33
|
+
* already has when that is not one of them — a publisher's own words, or an
|
|
34
|
+
* older listing's — so the menu never opens on a value it does not offer.
|
|
35
|
+
*/ export function siteStarterCategoryOptions(current) {
|
|
36
|
+
const kept = currentOf(current);
|
|
37
|
+
return kept && !SITE_STARTER_CATEGORIES.includes(kept) ? [
|
|
38
|
+
...SITE_STARTER_CATEGORIES,
|
|
39
|
+
kept
|
|
40
|
+
] : [
|
|
41
|
+
...SITE_STARTER_CATEGORIES
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A listing edit's category for a starter, from `category`, or from the first
|
|
46
|
+
* of `categories` as the native listing editors send it. A kind of site is
|
|
47
|
+
* taken, and so is the category the starter already has; an empty value
|
|
48
|
+
* clears it. Anything else is refused in the words the editor's field uses.
|
|
49
|
+
*/ export function siteStarterCategoryUpdate(body, current) {
|
|
50
|
+
var _body_categories_;
|
|
51
|
+
const raw = body.category !== undefined ? body.category : Array.isArray(body.categories) ? (_body_categories_ = body.categories[0]) != null ? _body_categories_ : '' : undefined;
|
|
52
|
+
if (raw === undefined) return {
|
|
53
|
+
ok: true,
|
|
54
|
+
category: undefined
|
|
55
|
+
};
|
|
56
|
+
if (typeof raw !== 'string') {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
error: 'Pick the kind of site this starter is from the Category menu'
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const next = raw.trim();
|
|
63
|
+
if (!next) return {
|
|
64
|
+
ok: true,
|
|
65
|
+
category: null
|
|
66
|
+
};
|
|
67
|
+
if (next.length <= SITE_STARTER_CATEGORY_MAX && (SITE_STARTER_CATEGORIES.includes(next) || next === currentOf(current))) {
|
|
68
|
+
return {
|
|
69
|
+
ok: true,
|
|
70
|
+
category: next
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
error: 'Pick the kind of site this starter is from the Category menu'
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
//# sourceMappingURL=starter-category.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/starter-category.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SITE_STYLES } from '@aglyn/aglyn/app-utils/site-styles'\n\n/**\n * What a site starter is filed under (AGL-4049): the kind of site it is, by\n * the style names a person picks a style by (\"Home services & trades\"). The\n * platform's seed files each kind's starters under that name, and the starter\n * shelf groups by it.\n *\n * A starter keeps its category in its own `category` field, as its publish\n * writes it — never the plugin taxonomy's `categories` list, whose fixed\n * values (\"analytics\", \"seo\", …) describe what a plugin does, not a site.\n *\n * Pure data and readers, safe for any surface to import.\n */\n\n/** The kinds of site a starter can be filed under, in the order a person sees the styles. */\nexport const SITE_STARTER_CATEGORIES: readonly string[] = SITE_STYLES.map(\n (style) => style.name,\n)\n\n/** The most characters a category holds, as the starter publish caps it. */\nexport const SITE_STARTER_CATEGORY_MAX = 40\n\nconst currentOf = (current: unknown): string =>\n typeof current === 'string' ? current.trim() : ''\n\n/**\n * The Category menu for a starter: every kind of site, and the category it\n * already has when that is not one of them — a publisher's own words, or an\n * older listing's — so the menu never opens on a value it does not offer.\n */\nexport function siteStarterCategoryOptions(current: unknown): string[] {\n const kept = currentOf(current)\n return kept && !SITE_STARTER_CATEGORIES.includes(kept)\n ? [...SITE_STARTER_CATEGORIES, kept]\n : [...SITE_STARTER_CATEGORIES]\n}\n\n/** What a starter's listing edit does to its category. */\nexport type SiteStarterCategoryUpdate =\n /** `undefined`: the save names no category; `null`: it clears it. */\n | { ok: true; category: string | null | undefined }\n | { ok: false; error: string }\n\n/**\n * A listing edit's category for a starter, from `category`, or from the first\n * of `categories` as the native listing editors send it. A kind of site is\n * taken, and so is the category the starter already has; an empty value\n * clears it. Anything else is refused in the words the editor's field uses.\n */\nexport function siteStarterCategoryUpdate(\n body: { category?: unknown; categories?: unknown },\n current: unknown,\n): SiteStarterCategoryUpdate {\n const raw =\n body.category !== undefined\n ? body.category\n : Array.isArray(body.categories)\n ? (body.categories[0] ?? '')\n : undefined\n if (raw === undefined) return { ok: true, category: undefined }\n if (typeof raw !== 'string') {\n return { ok: false, error: 'Pick the kind of site this starter is from the Category menu' }\n }\n const next = raw.trim()\n if (!next) return { ok: true, category: null }\n if (\n next.length <= SITE_STARTER_CATEGORY_MAX &&\n (SITE_STARTER_CATEGORIES.includes(next) || next === currentOf(current))\n ) {\n return { ok: true, category: next }\n }\n return { ok: false, error: 'Pick the kind of site this starter is from the Category menu' }\n}\n"],"names":["SITE_STYLES","SITE_STARTER_CATEGORIES","map","style","name","SITE_STARTER_CATEGORY_MAX","currentOf","current","trim","siteStarterCategoryOptions","kept","includes","siteStarterCategoryUpdate","body","raw","category","undefined","Array","isArray","categories","ok","error","next","length"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,WAAW,QAAQ,qCAAoC;AAEhE;;;;;;;;;;;CAWC,GAED,2FAA2F,GAC3F,OAAO,MAAMC,0BAA6CD,YAAYE,GAAG,CACvE,CAACC,QAAUA,MAAMC,IAAI,EACtB;AAED,0EAA0E,GAC1E,OAAO,MAAMC,4BAA4B,GAAE;AAE3C,MAAMC,YAAY,CAACC,UACjB,OAAOA,YAAY,WAAWA,QAAQC,IAAI,KAAK;AAEjD;;;;CAIC,GACD,OAAO,SAASC,2BAA2BF,OAAgB;IACzD,MAAMG,OAAOJ,UAAUC;IACvB,OAAOG,QAAQ,CAACT,wBAAwBU,QAAQ,CAACD,QAC7C;WAAIT;QAAyBS;KAAK,GAClC;WAAIT;KAAwB;AAClC;AAQA;;;;;CAKC,GACD,OAAO,SAASW,0BACdC,IAAkD,EAClDN,OAAgB;QAMPM;IAJT,MAAMC,MACJD,KAAKE,QAAQ,KAAKC,YACdH,KAAKE,QAAQ,GACbE,MAAMC,OAAO,CAACL,KAAKM,UAAU,KAC1BN,oBAAAA,KAAKM,UAAU,CAAC,EAAE,YAAlBN,oBAAsB,KACvBG;IACR,IAAIF,QAAQE,WAAW,OAAO;QAAEI,IAAI;QAAML,UAAUC;IAAU;IAC9D,IAAI,OAAOF,QAAQ,UAAU;QAC3B,OAAO;YAAEM,IAAI;YAAOC,OAAO;QAA+D;IAC5F;IACA,MAAMC,OAAOR,IAAIN,IAAI;IACrB,IAAI,CAACc,MAAM,OAAO;QAAEF,IAAI;QAAML,UAAU;IAAK;IAC7C,IACEO,KAAKC,MAAM,IAAIlB,6BACdJ,CAAAA,wBAAwBU,QAAQ,CAACW,SAASA,SAAShB,UAAUC,QAAO,GACrE;QACA,OAAO;YAAEa,IAAI;YAAML,UAAUO;QAAK;IACpC;IACA,OAAO;QAAEF,IAAI;QAAOC,OAAO;IAA+D;AAC5F"}
|
|
@@ -134,6 +134,33 @@ export interface StarterCardFact {
|
|
|
134
134
|
* of the marketplace (`design`), not a kind of site.
|
|
135
135
|
*/
|
|
136
136
|
export declare function starterCardFacts(row: SiteStarterSummary): StarterCardFact[];
|
|
137
|
+
/** Where the console reads the answers an AI starter carries. */
|
|
138
|
+
export declare const STARTER_ANSWERS_PATH = "/api/marketplace/starters/answers";
|
|
139
|
+
/** One AI starter's answers, asked for a workspace. */
|
|
140
|
+
export interface StarterAnswersQuery {
|
|
141
|
+
orgId: string;
|
|
142
|
+
listingId: string;
|
|
143
|
+
}
|
|
144
|
+
/** What the route answers: the starter's answers, and its facets. */
|
|
145
|
+
export interface StarterAnswersAnswer {
|
|
146
|
+
answers: Record<string, unknown>;
|
|
147
|
+
facets: SiteStarterFacets;
|
|
148
|
+
}
|
|
149
|
+
/** The query string for one starter's answers, `orgId` then `listingId`. */
|
|
150
|
+
export declare function starterAnswersParams(input: StarterAnswersQuery): URLSearchParams;
|
|
151
|
+
/**
|
|
152
|
+
* The route's reading of the question, or the refusal a 400 answers with:
|
|
153
|
+
* the workspace, and a listing id as a listing may have one, since it
|
|
154
|
+
* becomes a document path on the way to the listing.
|
|
155
|
+
*/
|
|
156
|
+
export declare function starterAnswersQueryFrom(params: URLSearchParams): StarterAnswersQuery | {
|
|
157
|
+
error: string;
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* The route's answer, read defensively: the answers as a plain object, and
|
|
161
|
+
* the facets as a summary reads them; `null` for anything without answers.
|
|
162
|
+
*/
|
|
163
|
+
export declare function readStarterAnswersAnswer(json: unknown): StarterAnswersAnswer | null;
|
|
137
164
|
/** Where a purchase is opened. */
|
|
138
165
|
export declare const STARTER_CHECKOUT_PATH = "/api/marketplace/checkout";
|
|
139
166
|
/**
|
|
@@ -238,6 +238,48 @@ const idOrNull = (value)=>typeof value === 'string' && value.trim() ? value.trim
|
|
|
238
238
|
});
|
|
239
239
|
return facts;
|
|
240
240
|
}
|
|
241
|
+
// ── An AI starter's answers (AGL-4049 phase 4) ─────────────────────────────
|
|
242
|
+
/** Where the console reads the answers an AI starter carries. */ export const STARTER_ANSWERS_PATH = '/api/marketplace/starters/answers';
|
|
243
|
+
/** The query string for one starter's answers, `orgId` then `listingId`. */ export function starterAnswersParams(input) {
|
|
244
|
+
const params = new URLSearchParams();
|
|
245
|
+
params.set('orgId', trimmed(input.orgId));
|
|
246
|
+
params.set('listingId', trimmed(input.listingId));
|
|
247
|
+
return params;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* The route's reading of the question, or the refusal a 400 answers with:
|
|
251
|
+
* the workspace, and a listing id as a listing may have one, since it
|
|
252
|
+
* becomes a document path on the way to the listing.
|
|
253
|
+
*/ export function starterAnswersQueryFrom(params) {
|
|
254
|
+
const orgId = trimmed(params.get('orgId'));
|
|
255
|
+
if (!ORG_ID.test(orgId)) return {
|
|
256
|
+
error: 'Missing orgId'
|
|
257
|
+
};
|
|
258
|
+
const listingId = trimmed(params.get('listingId'));
|
|
259
|
+
if (!LISTING_ID.test(listingId)) return {
|
|
260
|
+
error: 'Unknown starter'
|
|
261
|
+
};
|
|
262
|
+
return {
|
|
263
|
+
orgId,
|
|
264
|
+
listingId
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* The route's answer, read defensively: the answers as a plain object, and
|
|
269
|
+
* the facets as a summary reads them; `null` for anything without answers.
|
|
270
|
+
*/ export function readStarterAnswersAnswer(json) {
|
|
271
|
+
var _readStarterSummary;
|
|
272
|
+
if (!isRecord(json) || !isRecord(json['answers'])) return null;
|
|
273
|
+
const facets = (_readStarterSummary = readStarterSummary({
|
|
274
|
+
listingId: 'answers',
|
|
275
|
+
facets: json['facets']
|
|
276
|
+
})) == null ? void 0 : _readStarterSummary.facets;
|
|
277
|
+
if (!facets) return null;
|
|
278
|
+
return {
|
|
279
|
+
answers: json['answers'],
|
|
280
|
+
facets
|
|
281
|
+
};
|
|
282
|
+
}
|
|
241
283
|
/** Where a purchase is opened. */ export const STARTER_CHECKOUT_PATH = '/api/marketplace/checkout';
|
|
242
284
|
/**
|
|
243
285
|
* The one place other than the marketplace a purchase may send its buyer
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/starter-query.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport { isSiteStyleId, siteStyleOf } from '@aglyn/aglyn/app-utils/site-styles'\nimport type {\n SiteStarterFacets,\n SiteStarterMode,\n} from '@aglyn/aglyn/app-utils/site-starter'\nimport type { SiteStarterSummary } from '@aglyn/aglyn/plugin-manager/plugin-site-starters'\nimport {\n SITE_STARTER_KNOWN_IDS,\n SITE_STARTER_SEARCH_MAX,\n siteStarterSearchLimit,\n} from './site-starter-listing'\n\n/**\n * The starter search as a request and an answer (AGL-4049): what \"Choose\n * your starter\" asks `GET /api/marketplace/starters`, how the route reads the\n * question back, and how a card words what it shows.\n *\n * Pure and dependency-light on purpose, so the native apps mirror it line for\n * line (phase 5's `StarterQuery`): the same parameters in the same order, the\n * same defensive read of the answer, the same \"Free\" and \"$12\".\n */\n\n/** Where the console asks. */\nexport const STARTER_SEARCH_PATH = '/api/marketplace/starters'\n\n/** The longest search a request carries; the rest is dropped, not refused. */\nexport const STARTER_SEARCH_WORD_MAX = 200\n\n/** The facets a search may narrow to by query parameter, in the order they are written. */\nexport const STARTER_FILTER_KEYS = ['kind', 'base', 'fonts'] as const\nexport type StarterFilterKey = (typeof STARTER_FILTER_KEYS)[number]\n\n/** The names the cards and the filters show for the ids a starter carries. */\nexport interface StarterSearchLabels {\n /** A built-in theme's short name (`material3`) to the name the theme picker shows. */\n base: Record<string, string>\n /** A font pairing id to its families, as a person reads them. */\n fonts: Record<string, string>\n}\n\n/** What the route answers: the starters as summaries, and the names for their ids. */\nexport interface StarterSearchAnswer {\n rows: SiteStarterSummary[]\n labels: StarterSearchLabels\n}\n\n/** One search, as the console and the native apps build it. */\nexport interface StarterQueryInput {\n /** The workspace the site will belong to. */\n orgId: string\n /** How the site will be filled: its pages as they are, or by the AI site start. */\n mode: SiteStarterMode\n /** The person's words, as typed. */\n word?: string\n /** The facets the person narrowed to; an absent one is any. */\n filter?: Partial<SiteStarterFacets>\n /** At most this many; the route caps it. */\n limit?: number\n /**\n * A listing the answer must carry whatever the search finds (AGL-4049):\n * the person's own pick, restored on the way back from buying it, which a\n * search for other words or another mode would not list.\n */\n include?: string | null\n}\n\n/** One search, as the route reads it back, every part checked. */\nexport interface StarterQuery {\n orgId: string\n mode: SiteStarterMode\n word: string\n /** `mode` is always among them, so the source asks for that mode only. */\n filter: Partial<SiteStarterFacets>\n limit: number\n /** The listing to carry whatever the search finds, or `null`. */\n include: string | null\n}\n\nconst ORG_ID = /^[A-Za-z0-9_-]{1,128}$/\nconst LISTING_ID = /^[A-Za-z0-9_-]{1,128}$/\nconst FACET_ID = /^[a-z0-9][a-z0-9-]{0,63}$/\n\nconst trimmed = (value: unknown): string =>\n typeof value === 'string' ? value.trim() : ''\n\n/**\n * The query string for one search, its parameters always in one order —\n * `orgId`, `mode`, `q`, `kind`, `base`, `fonts`, `limit`, `include` — so the\n * same search is the same URL, and a hook can key a request on it.\n */\nexport function starterQueryParams(input: StarterQueryInput): URLSearchParams {\n const params = new URLSearchParams()\n params.set('orgId', trimmed(input.orgId))\n params.set('mode', input.mode === 'ai' ? 'ai' : 'base')\n const word = trimmed(input.word).slice(0, STARTER_SEARCH_WORD_MAX)\n if (word) params.set('q', word)\n for (const key of STARTER_FILTER_KEYS) {\n const value = trimmed(input.filter?.[key])\n if (value) params.set(key, value)\n }\n params.set(\n 'limit',\n String(siteStarterSearchLimit(input.limit ?? SITE_STARTER_SEARCH_MAX) || SITE_STARTER_SEARCH_MAX),\n )\n const include = trimmed(input.include)\n if (LISTING_ID.test(include)) params.set('include', include)\n return params\n}\n\n/**\n * The route's reading of a search, or the refusal it answers with a 400.\n *\n * A base theme or a font pairing is checked against the lists a starter may\n * name (`SITE_STARTER_KNOWN_IDS`), and a kind against the styles a person\n * chooses among (core's `SITE_STYLES`, which are the kinds of site), so a\n * mistyped one is named rather than answered with nothing.\n */\nexport function starterQueryFrom(\n params: URLSearchParams,\n): StarterQuery | { error: string } {\n const orgId = trimmed(params.get('orgId'))\n if (!ORG_ID.test(orgId)) return { error: 'Missing orgId' }\n const askedMode = trimmed(params.get('mode'))\n if (askedMode && askedMode !== 'base' && askedMode !== 'ai') {\n return { error: 'Unknown mode' }\n }\n const mode: SiteStarterMode = askedMode === 'ai' ? 'ai' : 'base'\n const filter: Partial<SiteStarterFacets> = { mode }\n const kind = trimmed(params.get('kind'))\n if (kind) {\n if (!FACET_ID.test(kind) || !isSiteStyleId(kind)) return { error: 'Unknown kind of site' }\n filter.kind = kind\n }\n const base = trimmed(params.get('base'))\n if (base) {\n if (!SITE_STARTER_KNOWN_IDS.bases?.has(base)) return { error: 'Unknown base theme' }\n filter.base = base\n }\n const fonts = trimmed(params.get('fonts'))\n if (fonts) {\n if (!SITE_STARTER_KNOWN_IDS.fonts?.has(fonts)) return { error: 'Unknown font pairing' }\n filter.fonts = fonts\n }\n const include = trimmed(params.get('include'))\n if (include && !LISTING_ID.test(include)) return { error: 'Unknown starter' }\n return {\n orgId,\n mode,\n word: trimmed(params.get('q')).slice(0, STARTER_SEARCH_WORD_MAX),\n filter,\n limit: siteStarterSearchLimit(params.get('limit')) || SITE_STARTER_SEARCH_MAX,\n include: include || null,\n }\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n !!value && typeof value === 'object' && !Array.isArray(value)\n\nconst textOr = (value: unknown, fallback: string): string =>\n typeof value === 'string' ? value : fallback\n\nconst idOrNull = (value: unknown): string | null =>\n typeof value === 'string' && value.trim() ? value.trim() : null\n\n/**\n * One starter as a card shows it, read field by field, or `null` for a row\n * with no listing id.\n *\n * The route answers each row through this, as the console and the apps read\n * it, so what leaves the server is a summary and nothing else: a source that\n * handed back a paid AI starter's answers, or its pages, would have them\n * dropped here. Each field falls back the way a card needs it to: a missing\n * price is free, a missing picture is none, a page count is a whole number\n * from zero, a mode is `base` unless it says `ai`, and `owned` is kept only\n * where the answer gave it as a yes or a no.\n */\nexport function readStarterSummary(value: unknown): SiteStarterSummary | null {\n if (!isRecord(value)) return null\n const listingId = idOrNull(value['listingId'])\n if (!listingId) return null\n const price = Number(value['priceUsd'])\n const facets = isRecord(value['facets']) ? value['facets'] : {}\n const pages = Number(facets['pages'])\n const preview = value['previewImageUrl']\n return {\n listingId,\n displayName: textOr(value['displayName'], ''),\n description: textOr(value['description'], ''),\n authorName: textOr(value['authorName'], ''),\n official: value['official'] === true,\n priceUsd: Number.isFinite(price) && price > 0 ? price : 0,\n previewImageUrl: typeof preview === 'string' && preview ? preview : null,\n facets: {\n kind: idOrNull(facets['kind']),\n base: idOrNull(facets['base']),\n fonts: idOrNull(facets['fonts']),\n pages: Number.isFinite(pages) && pages > 0 ? Math.floor(pages) : 0,\n mode: facets['mode'] === 'ai' ? 'ai' : 'base',\n },\n category: idOrNull(value['category']),\n // Only where the answer says: absent is \"the search does not know\".\n ...(typeof value['owned'] === 'boolean' ? { owned: value['owned'] } : {}),\n }\n}\n\n/** A map of id to name, keeping only string names under non-empty ids. */\nconst namesOf = (value: unknown): Record<string, string> => {\n if (!isRecord(value)) return {}\n const names: Record<string, string> = {}\n for (const [id, name] of Object.entries(value)) {\n if (id && typeof name === 'string' && name) names[id] = name\n }\n return names\n}\n\n/**\n * The route's answer, read defensively: rows that are not starters are\n * dropped, as is a duplicate of a listing already read, and missing labels\n * are empty. Never throws, whatever it is handed.\n */\nexport function parseStarterSearchAnswer(json: unknown): StarterSearchAnswer {\n const body = isRecord(json) ? json : {}\n const seen = new Set<string>()\n const rows: SiteStarterSummary[] = []\n for (const value of Array.isArray(body['rows']) ? body['rows'] : []) {\n const row = readStarterSummary(value)\n if (!row || seen.has(row.listingId)) continue\n seen.add(row.listingId)\n rows.push(row)\n }\n const labels = isRecord(body['labels']) ? body['labels'] : {}\n return { rows, labels: { base: namesOf(labels['base']), fonts: namesOf(labels['fonts']) } }\n}\n\n/**\n * A starter's price as a card shows it: \"Free\" at zero, whole dollars as\n * \"$12\", and cents only where there are some (\"$12.50\").\n */\nexport function starterPriceLabel(usd: number): string {\n if (!Number.isFinite(usd) || usd <= 0) return 'Free'\n return Number.isInteger(usd) ? `$${usd}` : `$${usd.toFixed(2)}`\n}\n\n/** How many pages a starter creates, as a card says it, or `null` when it does not say. */\nexport function starterPagesLabel(pages: number): string | null {\n if (!Number.isFinite(pages) || pages < 1) return null\n const whole = Math.floor(pages)\n return whole === 1 ? '1 page' : `${whole} pages`\n}\n\n/** One fact a starter's card shows as a chip, keyed so a surface can style each kind its own way. */\nexport interface StarterCardFact {\n key: 'price' | 'official' | 'ai' | 'kind' | 'pages'\n label: string\n}\n\n/**\n * The chips on a starter's card, in the order they are read: the price,\n * every card carrying one (\"Free\" included, so a missing chip never reads as\n * a forgotten price); the platform's name for its own; \"AI\" for a starter the\n * AI site start fills; the kind of site in words; and its page count.\n *\n * The kind is the style's own name for the listing's kind of site (core's\n * `SITE_STYLES`), the same words the kind filters show — never the listing's\n * marketplace category, which for a starter that names no kind is a shelf\n * of the marketplace (`design`), not a kind of site.\n */\nexport function starterCardFacts(row: SiteStarterSummary): StarterCardFact[] {\n const facts: StarterCardFact[] = [{ key: 'price', label: starterPriceLabel(row.priceUsd) }]\n if (row.official) facts.push({ key: 'official', label: PLATFORM_BRAND_NAME })\n if (row.facets.mode === 'ai') facts.push({ key: 'ai', label: 'AI' })\n const kind = siteStyleOf(row.facets.kind)?.name\n if (kind) facts.push({ key: 'kind', label: kind })\n const pages = starterPagesLabel(row.facets.pages)\n if (pages) facts.push({ key: 'pages', label: pages })\n return facts\n}\n\n/** Where a purchase is opened. */\nexport const STARTER_CHECKOUT_PATH = '/api/marketplace/checkout'\n\n/**\n * The one place other than the marketplace a purchase may send its buyer\n * back to (AGL-4049): Create site, reopened on the starter they picked. A\n * name the checkout turns into an address itself, never an address the\n * browser hands it.\n */\nexport const STARTER_CHECKOUT_RETURN = 'create-site'\nexport type StarterCheckoutReturn = typeof STARTER_CHECKOUT_RETURN\n\n/**\n * A checkout body's `return`, read: `create-site`, `null` when it names none\n * (the marketplace, as always), or the refusal a 400 answers with.\n */\nexport function readStarterCheckoutReturn(value: unknown): StarterCheckoutReturn | null | { error: string } {\n if (value === undefined || value === null || value === '') return null\n return value === STARTER_CHECKOUT_RETURN ? STARTER_CHECKOUT_RETURN : { error: 'Unknown return' }\n}\n\n/** The body \"Buy and continue\" sends: the starter, the workspace buying it, and the way back. */\nexport function starterCheckoutBody(input: {\n listingId: string\n orgId: string\n gaClientId?: string | null\n}): Record<string, string> {\n return {\n listingId: input.listingId,\n orgId: input.orgId,\n return: STARTER_CHECKOUT_RETURN,\n ...(input.gaClientId ? { gaClientId: input.gaClientId } : {}),\n }\n}\n"],"names":["PLATFORM_BRAND_NAME","isSiteStyleId","siteStyleOf","SITE_STARTER_KNOWN_IDS","SITE_STARTER_SEARCH_MAX","siteStarterSearchLimit","STARTER_SEARCH_PATH","STARTER_SEARCH_WORD_MAX","STARTER_FILTER_KEYS","ORG_ID","LISTING_ID","FACET_ID","trimmed","value","trim","starterQueryParams","input","params","URLSearchParams","set","orgId","mode","word","slice","key","filter","String","limit","include","test","starterQueryFrom","get","error","askedMode","kind","base","bases","has","fonts","isRecord","Array","isArray","textOr","fallback","idOrNull","readStarterSummary","listingId","price","Number","facets","pages","preview","displayName","description","authorName","official","priceUsd","isFinite","previewImageUrl","Math","floor","category","owned","namesOf","names","id","name","Object","entries","parseStarterSearchAnswer","json","body","seen","Set","rows","row","add","push","labels","starterPriceLabel","usd","isInteger","toFixed","starterPagesLabel","whole","starterCardFacts","facts","label","STARTER_CHECKOUT_PATH","STARTER_CHECKOUT_RETURN","readStarterCheckoutReturn","undefined","starterCheckoutBody","return","gaClientId"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/starter-query.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport { isSiteStyleId, siteStyleOf } from '@aglyn/aglyn/app-utils/site-styles'\nimport type {\n SiteStarterFacets,\n SiteStarterMode,\n} from '@aglyn/aglyn/app-utils/site-starter'\nimport type { SiteStarterSummary } from '@aglyn/aglyn/plugin-manager/plugin-site-starters'\nimport {\n SITE_STARTER_KNOWN_IDS,\n SITE_STARTER_SEARCH_MAX,\n siteStarterSearchLimit,\n} from './site-starter-listing'\n\n/**\n * The starter search as a request and an answer (AGL-4049): what \"Choose\n * your starter\" asks `GET /api/marketplace/starters`, how the route reads the\n * question back, and how a card words what it shows.\n *\n * Pure and dependency-light on purpose, so the native apps mirror it line for\n * line (phase 5's `StarterQuery`): the same parameters in the same order, the\n * same defensive read of the answer, the same \"Free\" and \"$12\".\n */\n\n/** Where the console asks. */\nexport const STARTER_SEARCH_PATH = '/api/marketplace/starters'\n\n/** The longest search a request carries; the rest is dropped, not refused. */\nexport const STARTER_SEARCH_WORD_MAX = 200\n\n/** The facets a search may narrow to by query parameter, in the order they are written. */\nexport const STARTER_FILTER_KEYS = ['kind', 'base', 'fonts'] as const\nexport type StarterFilterKey = (typeof STARTER_FILTER_KEYS)[number]\n\n/** The names the cards and the filters show for the ids a starter carries. */\nexport interface StarterSearchLabels {\n /** A built-in theme's short name (`material3`) to the name the theme picker shows. */\n base: Record<string, string>\n /** A font pairing id to its families, as a person reads them. */\n fonts: Record<string, string>\n}\n\n/** What the route answers: the starters as summaries, and the names for their ids. */\nexport interface StarterSearchAnswer {\n rows: SiteStarterSummary[]\n labels: StarterSearchLabels\n}\n\n/** One search, as the console and the native apps build it. */\nexport interface StarterQueryInput {\n /** The workspace the site will belong to. */\n orgId: string\n /** How the site will be filled: its pages as they are, or by the AI site start. */\n mode: SiteStarterMode\n /** The person's words, as typed. */\n word?: string\n /** The facets the person narrowed to; an absent one is any. */\n filter?: Partial<SiteStarterFacets>\n /** At most this many; the route caps it. */\n limit?: number\n /**\n * A listing the answer must carry whatever the search finds (AGL-4049):\n * the person's own pick, restored on the way back from buying it, which a\n * search for other words or another mode would not list.\n */\n include?: string | null\n}\n\n/** One search, as the route reads it back, every part checked. */\nexport interface StarterQuery {\n orgId: string\n mode: SiteStarterMode\n word: string\n /** `mode` is always among them, so the source asks for that mode only. */\n filter: Partial<SiteStarterFacets>\n limit: number\n /** The listing to carry whatever the search finds, or `null`. */\n include: string | null\n}\n\nconst ORG_ID = /^[A-Za-z0-9_-]{1,128}$/\nconst LISTING_ID = /^[A-Za-z0-9_-]{1,128}$/\nconst FACET_ID = /^[a-z0-9][a-z0-9-]{0,63}$/\n\nconst trimmed = (value: unknown): string =>\n typeof value === 'string' ? value.trim() : ''\n\n/**\n * The query string for one search, its parameters always in one order —\n * `orgId`, `mode`, `q`, `kind`, `base`, `fonts`, `limit`, `include` — so the\n * same search is the same URL, and a hook can key a request on it.\n */\nexport function starterQueryParams(input: StarterQueryInput): URLSearchParams {\n const params = new URLSearchParams()\n params.set('orgId', trimmed(input.orgId))\n params.set('mode', input.mode === 'ai' ? 'ai' : 'base')\n const word = trimmed(input.word).slice(0, STARTER_SEARCH_WORD_MAX)\n if (word) params.set('q', word)\n for (const key of STARTER_FILTER_KEYS) {\n const value = trimmed(input.filter?.[key])\n if (value) params.set(key, value)\n }\n params.set(\n 'limit',\n String(siteStarterSearchLimit(input.limit ?? SITE_STARTER_SEARCH_MAX) || SITE_STARTER_SEARCH_MAX),\n )\n const include = trimmed(input.include)\n if (LISTING_ID.test(include)) params.set('include', include)\n return params\n}\n\n/**\n * The route's reading of a search, or the refusal it answers with a 400.\n *\n * A base theme or a font pairing is checked against the lists a starter may\n * name (`SITE_STARTER_KNOWN_IDS`), and a kind against the styles a person\n * chooses among (core's `SITE_STYLES`, which are the kinds of site), so a\n * mistyped one is named rather than answered with nothing.\n */\nexport function starterQueryFrom(\n params: URLSearchParams,\n): StarterQuery | { error: string } {\n const orgId = trimmed(params.get('orgId'))\n if (!ORG_ID.test(orgId)) return { error: 'Missing orgId' }\n const askedMode = trimmed(params.get('mode'))\n if (askedMode && askedMode !== 'base' && askedMode !== 'ai') {\n return { error: 'Unknown mode' }\n }\n const mode: SiteStarterMode = askedMode === 'ai' ? 'ai' : 'base'\n const filter: Partial<SiteStarterFacets> = { mode }\n const kind = trimmed(params.get('kind'))\n if (kind) {\n if (!FACET_ID.test(kind) || !isSiteStyleId(kind)) return { error: 'Unknown kind of site' }\n filter.kind = kind\n }\n const base = trimmed(params.get('base'))\n if (base) {\n if (!SITE_STARTER_KNOWN_IDS.bases?.has(base)) return { error: 'Unknown base theme' }\n filter.base = base\n }\n const fonts = trimmed(params.get('fonts'))\n if (fonts) {\n if (!SITE_STARTER_KNOWN_IDS.fonts?.has(fonts)) return { error: 'Unknown font pairing' }\n filter.fonts = fonts\n }\n const include = trimmed(params.get('include'))\n if (include && !LISTING_ID.test(include)) return { error: 'Unknown starter' }\n return {\n orgId,\n mode,\n word: trimmed(params.get('q')).slice(0, STARTER_SEARCH_WORD_MAX),\n filter,\n limit: siteStarterSearchLimit(params.get('limit')) || SITE_STARTER_SEARCH_MAX,\n include: include || null,\n }\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n !!value && typeof value === 'object' && !Array.isArray(value)\n\nconst textOr = (value: unknown, fallback: string): string =>\n typeof value === 'string' ? value : fallback\n\nconst idOrNull = (value: unknown): string | null =>\n typeof value === 'string' && value.trim() ? value.trim() : null\n\n/**\n * One starter as a card shows it, read field by field, or `null` for a row\n * with no listing id.\n *\n * The route answers each row through this, as the console and the apps read\n * it, so what leaves the server is a summary and nothing else: a source that\n * handed back a paid AI starter's answers, or its pages, would have them\n * dropped here. Each field falls back the way a card needs it to: a missing\n * price is free, a missing picture is none, a page count is a whole number\n * from zero, a mode is `base` unless it says `ai`, and `owned` is kept only\n * where the answer gave it as a yes or a no.\n */\nexport function readStarterSummary(value: unknown): SiteStarterSummary | null {\n if (!isRecord(value)) return null\n const listingId = idOrNull(value['listingId'])\n if (!listingId) return null\n const price = Number(value['priceUsd'])\n const facets = isRecord(value['facets']) ? value['facets'] : {}\n const pages = Number(facets['pages'])\n const preview = value['previewImageUrl']\n return {\n listingId,\n displayName: textOr(value['displayName'], ''),\n description: textOr(value['description'], ''),\n authorName: textOr(value['authorName'], ''),\n official: value['official'] === true,\n priceUsd: Number.isFinite(price) && price > 0 ? price : 0,\n previewImageUrl: typeof preview === 'string' && preview ? preview : null,\n facets: {\n kind: idOrNull(facets['kind']),\n base: idOrNull(facets['base']),\n fonts: idOrNull(facets['fonts']),\n pages: Number.isFinite(pages) && pages > 0 ? Math.floor(pages) : 0,\n mode: facets['mode'] === 'ai' ? 'ai' : 'base',\n },\n category: idOrNull(value['category']),\n // Only where the answer says: absent is \"the search does not know\".\n ...(typeof value['owned'] === 'boolean' ? { owned: value['owned'] } : {}),\n }\n}\n\n/** A map of id to name, keeping only string names under non-empty ids. */\nconst namesOf = (value: unknown): Record<string, string> => {\n if (!isRecord(value)) return {}\n const names: Record<string, string> = {}\n for (const [id, name] of Object.entries(value)) {\n if (id && typeof name === 'string' && name) names[id] = name\n }\n return names\n}\n\n/**\n * The route's answer, read defensively: rows that are not starters are\n * dropped, as is a duplicate of a listing already read, and missing labels\n * are empty. Never throws, whatever it is handed.\n */\nexport function parseStarterSearchAnswer(json: unknown): StarterSearchAnswer {\n const body = isRecord(json) ? json : {}\n const seen = new Set<string>()\n const rows: SiteStarterSummary[] = []\n for (const value of Array.isArray(body['rows']) ? body['rows'] : []) {\n const row = readStarterSummary(value)\n if (!row || seen.has(row.listingId)) continue\n seen.add(row.listingId)\n rows.push(row)\n }\n const labels = isRecord(body['labels']) ? body['labels'] : {}\n return { rows, labels: { base: namesOf(labels['base']), fonts: namesOf(labels['fonts']) } }\n}\n\n/**\n * A starter's price as a card shows it: \"Free\" at zero, whole dollars as\n * \"$12\", and cents only where there are some (\"$12.50\").\n */\nexport function starterPriceLabel(usd: number): string {\n if (!Number.isFinite(usd) || usd <= 0) return 'Free'\n return Number.isInteger(usd) ? `$${usd}` : `$${usd.toFixed(2)}`\n}\n\n/** How many pages a starter creates, as a card says it, or `null` when it does not say. */\nexport function starterPagesLabel(pages: number): string | null {\n if (!Number.isFinite(pages) || pages < 1) return null\n const whole = Math.floor(pages)\n return whole === 1 ? '1 page' : `${whole} pages`\n}\n\n/** One fact a starter's card shows as a chip, keyed so a surface can style each kind its own way. */\nexport interface StarterCardFact {\n key: 'price' | 'official' | 'ai' | 'kind' | 'pages'\n label: string\n}\n\n/**\n * The chips on a starter's card, in the order they are read: the price,\n * every card carrying one (\"Free\" included, so a missing chip never reads as\n * a forgotten price); the platform's name for its own; \"AI\" for a starter the\n * AI site start fills; the kind of site in words; and its page count.\n *\n * The kind is the style's own name for the listing's kind of site (core's\n * `SITE_STYLES`), the same words the kind filters show — never the listing's\n * marketplace category, which for a starter that names no kind is a shelf\n * of the marketplace (`design`), not a kind of site.\n */\nexport function starterCardFacts(row: SiteStarterSummary): StarterCardFact[] {\n const facts: StarterCardFact[] = [{ key: 'price', label: starterPriceLabel(row.priceUsd) }]\n if (row.official) facts.push({ key: 'official', label: PLATFORM_BRAND_NAME })\n if (row.facets.mode === 'ai') facts.push({ key: 'ai', label: 'AI' })\n const kind = siteStyleOf(row.facets.kind)?.name\n if (kind) facts.push({ key: 'kind', label: kind })\n const pages = starterPagesLabel(row.facets.pages)\n if (pages) facts.push({ key: 'pages', label: pages })\n return facts\n}\n\n// ── An AI starter's answers (AGL-4049 phase 4) ─────────────────────────────\n\n/** Where the console reads the answers an AI starter carries. */\nexport const STARTER_ANSWERS_PATH = '/api/marketplace/starters/answers'\n\n/** One AI starter's answers, asked for a workspace. */\nexport interface StarterAnswersQuery {\n orgId: string\n listingId: string\n}\n\n/** What the route answers: the starter's answers, and its facets. */\nexport interface StarterAnswersAnswer {\n answers: Record<string, unknown>\n facets: SiteStarterFacets\n}\n\n/** The query string for one starter's answers, `orgId` then `listingId`. */\nexport function starterAnswersParams(input: StarterAnswersQuery): URLSearchParams {\n const params = new URLSearchParams()\n params.set('orgId', trimmed(input.orgId))\n params.set('listingId', trimmed(input.listingId))\n return params\n}\n\n/**\n * The route's reading of the question, or the refusal a 400 answers with:\n * the workspace, and a listing id as a listing may have one, since it\n * becomes a document path on the way to the listing.\n */\nexport function starterAnswersQueryFrom(params: URLSearchParams): StarterAnswersQuery | { error: string } {\n const orgId = trimmed(params.get('orgId'))\n if (!ORG_ID.test(orgId)) return { error: 'Missing orgId' }\n const listingId = trimmed(params.get('listingId'))\n if (!LISTING_ID.test(listingId)) return { error: 'Unknown starter' }\n return { orgId, listingId }\n}\n\n/**\n * The route's answer, read defensively: the answers as a plain object, and\n * the facets as a summary reads them; `null` for anything without answers.\n */\nexport function readStarterAnswersAnswer(json: unknown): StarterAnswersAnswer | null {\n if (!isRecord(json) || !isRecord(json['answers'])) return null\n const facets = readStarterSummary({ listingId: 'answers', facets: json['facets'] })?.facets\n if (!facets) return null\n return { answers: json['answers'], facets }\n}\n\n/** Where a purchase is opened. */\nexport const STARTER_CHECKOUT_PATH = '/api/marketplace/checkout'\n\n/**\n * The one place other than the marketplace a purchase may send its buyer\n * back to (AGL-4049): Create site, reopened on the starter they picked. A\n * name the checkout turns into an address itself, never an address the\n * browser hands it.\n */\nexport const STARTER_CHECKOUT_RETURN = 'create-site'\nexport type StarterCheckoutReturn = typeof STARTER_CHECKOUT_RETURN\n\n/**\n * A checkout body's `return`, read: `create-site`, `null` when it names none\n * (the marketplace, as always), or the refusal a 400 answers with.\n */\nexport function readStarterCheckoutReturn(value: unknown): StarterCheckoutReturn | null | { error: string } {\n if (value === undefined || value === null || value === '') return null\n return value === STARTER_CHECKOUT_RETURN ? STARTER_CHECKOUT_RETURN : { error: 'Unknown return' }\n}\n\n/** The body \"Buy and continue\" sends: the starter, the workspace buying it, and the way back. */\nexport function starterCheckoutBody(input: {\n listingId: string\n orgId: string\n gaClientId?: string | null\n}): Record<string, string> {\n return {\n listingId: input.listingId,\n orgId: input.orgId,\n return: STARTER_CHECKOUT_RETURN,\n ...(input.gaClientId ? { gaClientId: input.gaClientId } : {}),\n }\n}\n"],"names":["PLATFORM_BRAND_NAME","isSiteStyleId","siteStyleOf","SITE_STARTER_KNOWN_IDS","SITE_STARTER_SEARCH_MAX","siteStarterSearchLimit","STARTER_SEARCH_PATH","STARTER_SEARCH_WORD_MAX","STARTER_FILTER_KEYS","ORG_ID","LISTING_ID","FACET_ID","trimmed","value","trim","starterQueryParams","input","params","URLSearchParams","set","orgId","mode","word","slice","key","filter","String","limit","include","test","starterQueryFrom","get","error","askedMode","kind","base","bases","has","fonts","isRecord","Array","isArray","textOr","fallback","idOrNull","readStarterSummary","listingId","price","Number","facets","pages","preview","displayName","description","authorName","official","priceUsd","isFinite","previewImageUrl","Math","floor","category","owned","namesOf","names","id","name","Object","entries","parseStarterSearchAnswer","json","body","seen","Set","rows","row","add","push","labels","starterPriceLabel","usd","isInteger","toFixed","starterPagesLabel","whole","starterCardFacts","facts","label","STARTER_ANSWERS_PATH","starterAnswersParams","starterAnswersQueryFrom","readStarterAnswersAnswer","answers","STARTER_CHECKOUT_PATH","STARTER_CHECKOUT_RETURN","readStarterCheckoutReturn","undefined","starterCheckoutBody","return","gaClientId"],"mappings":"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"}
|