@lukoweb/apitogo 0.1.54 → 0.1.56
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/dist/cli/cli.js +831 -398
- package/dist/declarations/config/default-landing-content.d.ts +2 -0
- package/dist/declarations/config/landing-manifest.d.ts +82 -0
- package/dist/declarations/config/local-manifest.d.ts +82 -0
- package/dist/declarations/config/resolve-modules.d.ts +3 -1
- package/dist/declarations/config/validators/ModulesSchema.d.ts +259 -0
- package/dist/declarations/config/validators/ZudokuConfig.d.ts +57 -0
- package/dist/declarations/lib/authentication/header-nav-filter.d.ts +4 -0
- package/dist/declarations/lib/authentication/providers/dev-portal-auth-pages.d.ts +5 -5
- package/dist/declarations/lib/authentication/providers/dev-portal-utils.d.ts +1 -0
- package/dist/declarations/lib/components/HeaderAuthActions.d.ts +3 -0
- package/dist/declarations/lib/components/SiteTitle.d.ts +6 -0
- package/dist/declarations/lib/components/ThemeSwitch.d.ts +3 -1
- package/dist/declarations/lib/components/TopNavigation.d.ts +1 -1
- package/dist/declarations/lib/core/ZudokuContext.d.ts +1 -0
- package/dist/flat-config.d.ts +95 -38
- package/package.json +1 -1
- package/src/app/main.css +2 -2
- package/src/config/apitogo-config-core.ts +13 -0
- package/src/config/apitogo-config-loader.browser.ts +17 -0
- package/src/config/build-apitogo-config.ts +13 -4
- package/src/config/default-landing-content.ts +154 -0
- package/src/config/landing-manifest.ts +11 -0
- package/src/config/landing-openapi-showcase.ts +115 -0
- package/src/config/loader.ts +17 -3
- package/src/config/local-manifest.ts +114 -10
- package/src/config/resolve-modules.ts +121 -32
- package/src/config/validators/ModulesSchema.ts +109 -0
- package/src/config/validators/ZudokuConfig.ts +1 -0
- package/src/lib/authentication/auth-header-nav.ts +5 -15
- package/src/lib/authentication/header-nav-filter.ts +36 -0
- package/src/lib/authentication/providers/dev-portal-auth-pages.tsx +95 -35
- package/src/lib/authentication/providers/dev-portal-utils.ts +26 -7
- package/src/lib/authentication/providers/dev-portal.tsx +18 -8
- package/src/lib/components/Header.tsx +49 -39
- package/src/lib/components/HeaderAuthActions.tsx +36 -0
- package/src/lib/components/HeaderNavigation.tsx +11 -9
- package/src/lib/components/MobileTopNavigation.tsx +43 -24
- package/src/lib/components/SiteTitle.tsx +20 -0
- package/src/lib/components/ThemeSwitch.tsx +36 -2
- package/src/lib/components/TopNavigation.tsx +20 -33
- package/src/lib/core/ZudokuContext.ts +1 -0
- package/src/vite/config.ts +21 -0
- package/src/vite/plugin-auth.ts +4 -1
- package/src/vite/plugin-config.ts +39 -4
package/src/config/loader.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
loadApitogoConfig,
|
|
19
19
|
readPlanCatalogItems,
|
|
20
20
|
} from "./apitogo-config-loader.js";
|
|
21
|
+
import { buildApitogoConfig } from "./build-apitogo-config.js";
|
|
21
22
|
import { fileExists } from "./file-exists.js";
|
|
22
23
|
import {
|
|
23
24
|
isAuthenticationEnabled,
|
|
@@ -117,7 +118,18 @@ async function loadZudokuConfigWithMeta(
|
|
|
117
118
|
},
|
|
118
119
|
});
|
|
119
120
|
|
|
120
|
-
const
|
|
121
|
+
const importedConfig = module.default;
|
|
122
|
+
|
|
123
|
+
let config: ZudokuConfig;
|
|
124
|
+
try {
|
|
125
|
+
const fileConfig = loadApitogoConfig(rootDir);
|
|
126
|
+
config = buildApitogoConfig({
|
|
127
|
+
...fileConfig,
|
|
128
|
+
plugins: importedConfig.plugins,
|
|
129
|
+
});
|
|
130
|
+
} catch {
|
|
131
|
+
config = importedConfig;
|
|
132
|
+
}
|
|
121
133
|
|
|
122
134
|
validateConfig(config, configPath);
|
|
123
135
|
|
|
@@ -130,7 +142,7 @@ async function loadZudokuConfigWithMeta(
|
|
|
130
142
|
dependencies,
|
|
131
143
|
configPath,
|
|
132
144
|
},
|
|
133
|
-
__resolvedModules: resolveModulesConfig(config),
|
|
145
|
+
__resolvedModules: resolveModulesConfig(config, { rootDir }),
|
|
134
146
|
};
|
|
135
147
|
|
|
136
148
|
return configWithMetadata;
|
|
@@ -288,7 +300,9 @@ export async function loadZudokuConfig(
|
|
|
288
300
|
...transformedConfig.__meta,
|
|
289
301
|
...configWithManifestMeta.__meta,
|
|
290
302
|
},
|
|
291
|
-
__resolvedModules: resolveModulesConfig(transformedConfig
|
|
303
|
+
__resolvedModules: resolveModulesConfig(transformedConfig, {
|
|
304
|
+
rootDir: transformedConfig.__meta.rootDir,
|
|
305
|
+
}),
|
|
292
306
|
};
|
|
293
307
|
|
|
294
308
|
if (!process.env.APITOGO_JSON_ONLY) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import {
|
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
loadApitogoConfig,
|
|
9
9
|
readPlanCatalogItems,
|
|
10
10
|
} from "./apitogo-config-loader.js";
|
|
11
|
+
import {
|
|
12
|
+
type LandingManifestInput,
|
|
13
|
+
LandingManifestContentSchema,
|
|
14
|
+
} from "./landing-manifest.js";
|
|
11
15
|
|
|
12
16
|
/** @deprecated Legacy manifest filenames — apitogo.config.json is the only supported source. */
|
|
13
17
|
export const APITOGO_MANIFEST_BASE_FILENAME = "apitogo.json";
|
|
@@ -102,12 +106,19 @@ export const DevPortalLocalManifestSchema = z.object({
|
|
|
102
106
|
})
|
|
103
107
|
.optional(),
|
|
104
108
|
projectId: z.string().optional(),
|
|
109
|
+
landing: z
|
|
110
|
+
.object({
|
|
111
|
+
enabled: z.boolean().optional(),
|
|
112
|
+
content: LandingManifestContentSchema,
|
|
113
|
+
})
|
|
114
|
+
.optional(),
|
|
105
115
|
});
|
|
106
116
|
|
|
107
117
|
export type ManifestPlanInput = z.infer<typeof ManifestPlanInputSchema>;
|
|
108
118
|
export type DevPortalLocalManifest = z.infer<
|
|
109
119
|
typeof DevPortalLocalManifestSchema
|
|
110
120
|
>;
|
|
121
|
+
export type { LandingManifestInput };
|
|
111
122
|
|
|
112
123
|
export type DevPortalPreviewPlan = {
|
|
113
124
|
sku: string;
|
|
@@ -213,6 +224,105 @@ export function mergePlansBySku(
|
|
|
213
224
|
return Array.from(bySku.values());
|
|
214
225
|
}
|
|
215
226
|
|
|
227
|
+
export function mergeLandingContent(
|
|
228
|
+
existing: LandingManifestInput["content"] | undefined,
|
|
229
|
+
patch: LandingManifestInput["content"] | undefined,
|
|
230
|
+
): LandingManifestInput["content"] | undefined {
|
|
231
|
+
if (!patch) {
|
|
232
|
+
return existing;
|
|
233
|
+
}
|
|
234
|
+
if (!existing) {
|
|
235
|
+
return patch;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const merged = { ...existing, ...patch };
|
|
239
|
+
|
|
240
|
+
if (patch.hero) {
|
|
241
|
+
merged.hero = { ...existing.hero, ...patch.hero };
|
|
242
|
+
if (patch.hero.codeExample) {
|
|
243
|
+
merged.hero = {
|
|
244
|
+
...merged.hero,
|
|
245
|
+
codeExample: {
|
|
246
|
+
...existing.hero?.codeExample,
|
|
247
|
+
...patch.hero.codeExample,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (patch.features) {
|
|
254
|
+
const byTitle = new Map(
|
|
255
|
+
(existing.features ?? []).map((feature) => [feature.title, feature]),
|
|
256
|
+
);
|
|
257
|
+
for (const feature of patch.features) {
|
|
258
|
+
const prior = byTitle.get(feature.title);
|
|
259
|
+
byTitle.set(feature.title, prior ? { ...prior, ...feature } : feature);
|
|
260
|
+
}
|
|
261
|
+
merged.features = Array.from(byTitle.values());
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (patch.ticker) {
|
|
265
|
+
merged.ticker = { ...existing.ticker, ...patch.ticker };
|
|
266
|
+
if (patch.ticker.items) {
|
|
267
|
+
merged.ticker.items = patch.ticker.items;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (patch.trust) {
|
|
272
|
+
merged.trust = { ...existing.trust, ...patch.trust };
|
|
273
|
+
if (patch.trust.logos) {
|
|
274
|
+
merged.trust.logos = patch.trust.logos;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (patch.apiShowcase) {
|
|
279
|
+
merged.apiShowcase = { ...existing.apiShowcase, ...patch.apiShowcase };
|
|
280
|
+
if (patch.apiShowcase.items) {
|
|
281
|
+
const byPath = new Map(
|
|
282
|
+
(existing.apiShowcase?.items ?? []).map((item) => [item.path, item]),
|
|
283
|
+
);
|
|
284
|
+
for (const item of patch.apiShowcase.items) {
|
|
285
|
+
const prior = byPath.get(item.path);
|
|
286
|
+
byPath.set(item.path, prior ? { ...prior, ...item } : item);
|
|
287
|
+
}
|
|
288
|
+
merged.apiShowcase.items = Array.from(byPath.values());
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (patch.pricingCta) {
|
|
293
|
+
merged.pricingCta = { ...existing.pricingCta, ...patch.pricingCta };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (patch.primaryAction) {
|
|
297
|
+
merged.primaryAction = { ...existing.primaryAction, ...patch.primaryAction };
|
|
298
|
+
}
|
|
299
|
+
if (patch.secondaryAction) {
|
|
300
|
+
merged.secondaryAction = {
|
|
301
|
+
...existing.secondaryAction,
|
|
302
|
+
...patch.secondaryAction,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return merged;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function mergeLandingManifest(
|
|
310
|
+
existing: LandingManifestInput | undefined,
|
|
311
|
+
patch: LandingManifestInput | undefined,
|
|
312
|
+
): LandingManifestInput | undefined {
|
|
313
|
+
if (!patch) {
|
|
314
|
+
return existing;
|
|
315
|
+
}
|
|
316
|
+
if (!existing) {
|
|
317
|
+
return patch;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
enabled: patch.enabled ?? existing.enabled,
|
|
322
|
+
content: mergeLandingContent(existing.content, patch.content),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
216
326
|
export function mergeManifest(
|
|
217
327
|
base: DevPortalLocalManifest,
|
|
218
328
|
override: DevPortalLocalManifest,
|
|
@@ -240,6 +350,9 @@ export function mergeManifest(
|
|
|
240
350
|
if (override.plans) {
|
|
241
351
|
merged.plans = mergePlansBySku(base.plans, override.plans);
|
|
242
352
|
}
|
|
353
|
+
if (override.landing) {
|
|
354
|
+
merged.landing = mergeLandingManifest(base.landing, override.landing);
|
|
355
|
+
}
|
|
243
356
|
|
|
244
357
|
return merged;
|
|
245
358
|
}
|
|
@@ -277,15 +390,6 @@ export function parseLocalManifestJson(
|
|
|
277
390
|
}
|
|
278
391
|
}
|
|
279
392
|
|
|
280
|
-
async function fileExists(filePath: string): Promise<boolean> {
|
|
281
|
-
try {
|
|
282
|
-
await access(filePath);
|
|
283
|
-
return true;
|
|
284
|
-
} catch {
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
393
|
export async function readManifestFile(
|
|
290
394
|
rootDir: string,
|
|
291
395
|
filename: string,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_LANDING_CONTENT } from "./default-landing-content.js";
|
|
1
2
|
import {
|
|
2
3
|
type ResolvedModulesConfig,
|
|
3
4
|
ModulesSchema,
|
|
@@ -32,50 +33,136 @@ export const joinPanelPath = (basePath: string, routePath: string): string => {
|
|
|
32
33
|
return `/${`${base}/${route}`.replace(/^\/+/, "")}`;
|
|
33
34
|
};
|
|
34
35
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
title: "API Reference",
|
|
43
|
-
description:
|
|
44
|
-
"Publish interactive OpenAPI docs with a built-in playground and code samples.",
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
title: "Developer Portal",
|
|
48
|
-
description:
|
|
49
|
-
"Extend with landing pages, authentication, API keys, and subscription modules.",
|
|
50
|
-
},
|
|
51
|
-
];
|
|
36
|
+
const mergeLandingSection = <T extends { enabled?: boolean }>(
|
|
37
|
+
defaults: T | undefined,
|
|
38
|
+
override: T | undefined,
|
|
39
|
+
): T | undefined => {
|
|
40
|
+
if (override?.enabled === false) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
52
43
|
|
|
53
|
-
|
|
44
|
+
if (!defaults && !override) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { ...defaults, ...override } as T;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const buildDefaultLandingContent = (
|
|
54
52
|
config: ZudokuConfig,
|
|
55
|
-
content?: LandingContentConfig,
|
|
56
53
|
): ResolvedLandingContent => {
|
|
57
54
|
const siteTitle =
|
|
58
55
|
config.metadata?.applicationName ?? config.site?.title ?? "APIToGo";
|
|
59
56
|
|
|
60
57
|
return {
|
|
58
|
+
...DEFAULT_LANDING_CONTENT,
|
|
61
59
|
hero: {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
description:
|
|
65
|
-
content?.hero?.description ??
|
|
66
|
-
config.metadata?.description ??
|
|
67
|
-
"Ship beautiful API documentation and developer experiences with APIToGo.",
|
|
60
|
+
...DEFAULT_LANDING_CONTENT.hero,
|
|
61
|
+
title: `${siteTitle} APIs that ship`,
|
|
68
62
|
},
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
label: "Get started",
|
|
63
|
+
primaryAction: {
|
|
64
|
+
...DEFAULT_LANDING_CONTENT.primaryAction,
|
|
72
65
|
href: "/introduction",
|
|
66
|
+
label: "Get started",
|
|
73
67
|
},
|
|
74
|
-
secondaryAction:
|
|
75
|
-
|
|
68
|
+
secondaryAction: {
|
|
69
|
+
...DEFAULT_LANDING_CONTENT.secondaryAction,
|
|
76
70
|
href: "/api",
|
|
71
|
+
label: "API Reference",
|
|
72
|
+
},
|
|
73
|
+
pricingCta: DEFAULT_LANDING_CONTENT.pricingCta
|
|
74
|
+
? {
|
|
75
|
+
...DEFAULT_LANDING_CONTENT.pricingCta,
|
|
76
|
+
primaryAction: {
|
|
77
|
+
label: "Get started",
|
|
78
|
+
href: "/introduction",
|
|
79
|
+
},
|
|
80
|
+
secondaryAction: {
|
|
81
|
+
label: "See pricing",
|
|
82
|
+
href: "/pricing",
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
: undefined,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const resolveLandingContent = (
|
|
90
|
+
config: ZudokuConfig,
|
|
91
|
+
content?: LandingContentConfig,
|
|
92
|
+
options?: { useDefaults?: boolean },
|
|
93
|
+
): ResolvedLandingContent => {
|
|
94
|
+
const siteTitle =
|
|
95
|
+
config.metadata?.applicationName ?? config.site?.title ?? "APIToGo";
|
|
96
|
+
|
|
97
|
+
if (options?.useDefaults === true) {
|
|
98
|
+
const defaults = buildDefaultLandingContent(config);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
hero: {
|
|
102
|
+
...defaults.hero,
|
|
103
|
+
...content?.hero,
|
|
104
|
+
title: content?.hero?.title ?? defaults.hero?.title,
|
|
105
|
+
description:
|
|
106
|
+
content?.hero?.description ??
|
|
107
|
+
config.metadata?.description ??
|
|
108
|
+
defaults.hero?.description,
|
|
109
|
+
codeExample: content?.hero?.codeExample ?? defaults.hero?.codeExample,
|
|
110
|
+
titleAccent:
|
|
111
|
+
content?.hero?.titleAccent ??
|
|
112
|
+
(content?.hero?.title ? undefined : defaults.hero?.titleAccent),
|
|
113
|
+
},
|
|
114
|
+
featuresSection: mergeLandingSection(
|
|
115
|
+
defaults.featuresSection,
|
|
116
|
+
content?.featuresSection,
|
|
117
|
+
),
|
|
118
|
+
features: content?.features ?? defaults.features,
|
|
119
|
+
primaryAction: content?.primaryAction ?? defaults.primaryAction,
|
|
120
|
+
secondaryAction: content?.secondaryAction ?? defaults.secondaryAction,
|
|
121
|
+
showPoweredBy: content?.showPoweredBy ?? defaults.showPoweredBy,
|
|
122
|
+
ticker: mergeLandingSection(defaults.ticker, content?.ticker),
|
|
123
|
+
trust: mergeLandingSection(defaults.trust, content?.trust),
|
|
124
|
+
apiShowcase: mergeLandingSection(
|
|
125
|
+
defaults.apiShowcase,
|
|
126
|
+
content?.apiShowcase,
|
|
127
|
+
),
|
|
128
|
+
pricingCta: mergeLandingSection(defaults.pricingCta, content?.pricingCta),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const hasContent =
|
|
133
|
+
content !== undefined && Object.keys(content).length > 0;
|
|
134
|
+
|
|
135
|
+
if (!hasContent) {
|
|
136
|
+
return {
|
|
137
|
+
hero: { title: siteTitle },
|
|
138
|
+
showPoweredBy: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
hero: {
|
|
144
|
+
...content.hero,
|
|
145
|
+
title: content.hero?.title ?? siteTitle,
|
|
77
146
|
},
|
|
78
|
-
|
|
147
|
+
featuresSection: content.featuresSection
|
|
148
|
+
? mergeLandingSection(undefined, content.featuresSection)
|
|
149
|
+
: undefined,
|
|
150
|
+
features: content.features,
|
|
151
|
+
primaryAction: content.primaryAction,
|
|
152
|
+
secondaryAction: content.secondaryAction,
|
|
153
|
+
showPoweredBy: content.showPoweredBy ?? true,
|
|
154
|
+
ticker: content.ticker
|
|
155
|
+
? mergeLandingSection(undefined, content.ticker)
|
|
156
|
+
: undefined,
|
|
157
|
+
trust: content.trust
|
|
158
|
+
? mergeLandingSection(undefined, content.trust)
|
|
159
|
+
: undefined,
|
|
160
|
+
apiShowcase: content.apiShowcase
|
|
161
|
+
? mergeLandingSection(undefined, content.apiShowcase)
|
|
162
|
+
: undefined,
|
|
163
|
+
pricingCta: content.pricingCta
|
|
164
|
+
? mergeLandingSection(undefined, content.pricingCta)
|
|
165
|
+
: undefined,
|
|
79
166
|
};
|
|
80
167
|
};
|
|
81
168
|
|
|
@@ -172,7 +259,9 @@ export const resolveModulesConfig = (
|
|
|
172
259
|
path: landingModule?.path ?? "/",
|
|
173
260
|
component: landingModule?.component,
|
|
174
261
|
layout: landingModule?.layout ?? "landing",
|
|
175
|
-
content: resolveLandingContent(config, landingModule?.content
|
|
262
|
+
content: resolveLandingContent(config, landingModule?.content, {
|
|
263
|
+
useDefaults: landingModule?.useDefaults === true,
|
|
264
|
+
}),
|
|
176
265
|
},
|
|
177
266
|
userPanel: {
|
|
178
267
|
enabled: userPanelEnabled,
|
|
@@ -44,22 +44,102 @@ export const LandingLinkSchema = z.object({
|
|
|
44
44
|
export const LandingFeatureSchema = z.object({
|
|
45
45
|
title: z.string(),
|
|
46
46
|
description: z.string(),
|
|
47
|
+
icon: z.string().optional(),
|
|
47
48
|
});
|
|
48
49
|
|
|
50
|
+
export const LandingCodeExampleSchema = z
|
|
51
|
+
.object({
|
|
52
|
+
filename: z.string().optional(),
|
|
53
|
+
language: z.string().optional(),
|
|
54
|
+
code: z.string(),
|
|
55
|
+
})
|
|
56
|
+
.optional();
|
|
57
|
+
|
|
49
58
|
export const LandingHeroSchema = z
|
|
50
59
|
.object({
|
|
51
60
|
title: z.string().optional(),
|
|
61
|
+
titleAccent: z
|
|
62
|
+
.string()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Accent-colored word or phrase shown on a new line after the title."),
|
|
52
65
|
subtitle: z.string().optional(),
|
|
53
66
|
description: z.string().optional(),
|
|
67
|
+
badge: z.string().optional(),
|
|
68
|
+
highlights: z.array(z.string()).optional(),
|
|
69
|
+
codeExample: LandingCodeExampleSchema,
|
|
70
|
+
})
|
|
71
|
+
.optional();
|
|
72
|
+
|
|
73
|
+
export const LandingFeaturesSectionSchema = z
|
|
74
|
+
.object({
|
|
75
|
+
label: z.string().optional(),
|
|
76
|
+
title: z.string().optional(),
|
|
77
|
+
description: z.string().optional(),
|
|
78
|
+
})
|
|
79
|
+
.optional();
|
|
80
|
+
|
|
81
|
+
export const LandingTickerItemSchema = z.object({
|
|
82
|
+
label: z.string(),
|
|
83
|
+
value: z.string(),
|
|
84
|
+
change: z.string().optional(),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export const LandingTickerSchema = z
|
|
88
|
+
.object({
|
|
89
|
+
enabled: z.boolean().optional(),
|
|
90
|
+
items: z.array(LandingTickerItemSchema).optional(),
|
|
91
|
+
})
|
|
92
|
+
.optional();
|
|
93
|
+
|
|
94
|
+
export const LandingTrustSchema = z
|
|
95
|
+
.object({
|
|
96
|
+
enabled: z.boolean().optional(),
|
|
97
|
+
title: z.string().optional(),
|
|
98
|
+
logos: z.array(z.string()).optional(),
|
|
99
|
+
})
|
|
100
|
+
.optional();
|
|
101
|
+
|
|
102
|
+
export const LandingApiShowcaseItemSchema = z.object({
|
|
103
|
+
name: z.string(),
|
|
104
|
+
path: z.string(),
|
|
105
|
+
icon: z.string().optional(),
|
|
106
|
+
latency: z.string().optional(),
|
|
107
|
+
summary: z.string().optional(),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export const LandingApiShowcaseSchema = z
|
|
111
|
+
.object({
|
|
112
|
+
enabled: z.boolean().optional(),
|
|
113
|
+
title: z.string().optional(),
|
|
114
|
+
subtitle: z.string().optional(),
|
|
115
|
+
browseAllHref: z.string().optional(),
|
|
116
|
+
browseAllLabel: z.string().optional(),
|
|
117
|
+
autoFromOpenApi: z.boolean().optional(),
|
|
118
|
+
items: z.array(LandingApiShowcaseItemSchema).optional(),
|
|
119
|
+
})
|
|
120
|
+
.optional();
|
|
121
|
+
|
|
122
|
+
export const LandingPricingCtaSchema = z
|
|
123
|
+
.object({
|
|
124
|
+
enabled: z.boolean().optional(),
|
|
125
|
+
title: z.string().optional(),
|
|
126
|
+
description: z.string().optional(),
|
|
127
|
+
primaryAction: LandingLinkSchema.optional(),
|
|
128
|
+
secondaryAction: LandingLinkSchema.optional(),
|
|
54
129
|
})
|
|
55
130
|
.optional();
|
|
56
131
|
|
|
57
132
|
export const LandingContentSchema = z
|
|
58
133
|
.object({
|
|
59
134
|
hero: LandingHeroSchema,
|
|
135
|
+
featuresSection: LandingFeaturesSectionSchema,
|
|
60
136
|
features: z.array(LandingFeatureSchema).optional(),
|
|
61
137
|
primaryAction: LandingLinkSchema.optional(),
|
|
62
138
|
secondaryAction: LandingLinkSchema.optional(),
|
|
139
|
+
ticker: LandingTickerSchema,
|
|
140
|
+
trust: LandingTrustSchema,
|
|
141
|
+
apiShowcase: LandingApiShowcaseSchema,
|
|
142
|
+
pricingCta: LandingPricingCtaSchema,
|
|
63
143
|
showPoweredBy: z.boolean().optional(),
|
|
64
144
|
})
|
|
65
145
|
.optional();
|
|
@@ -128,6 +208,12 @@ export const LandingModuleSchema = ModuleToggleSchema.extend({
|
|
|
128
208
|
content: LandingContentSchema.describe(
|
|
129
209
|
"Default landing page content when no custom component is provided.",
|
|
130
210
|
),
|
|
211
|
+
useDefaults: z
|
|
212
|
+
.boolean()
|
|
213
|
+
.optional()
|
|
214
|
+
.describe(
|
|
215
|
+
"When true, merge DEFAULT_LANDING_CONTENT for unset sections. When false or omitted, only configured sections are shown.",
|
|
216
|
+
),
|
|
131
217
|
});
|
|
132
218
|
|
|
133
219
|
export const UserManagementModuleSchema = ModuleToggleSchema.extend({
|
|
@@ -246,15 +332,38 @@ export type ProfileContentConfig = z.infer<typeof ProfileContentSchema>;
|
|
|
246
332
|
export type PlansContentConfig = z.infer<typeof PlansContentSchema>;
|
|
247
333
|
export type PlanTierConfig = z.infer<typeof PlanTierSchema>;
|
|
248
334
|
|
|
335
|
+
export type LandingCodeExampleConfig = z.infer<typeof LandingCodeExampleSchema>;
|
|
336
|
+
export type LandingTickerItemConfig = z.infer<typeof LandingTickerItemSchema>;
|
|
337
|
+
export type LandingTickerConfig = z.infer<typeof LandingTickerSchema>;
|
|
338
|
+
export type LandingTrustConfig = z.infer<typeof LandingTrustSchema>;
|
|
339
|
+
export type LandingApiShowcaseItemConfig = z.infer<
|
|
340
|
+
typeof LandingApiShowcaseItemSchema
|
|
341
|
+
>;
|
|
342
|
+
export type LandingApiShowcaseConfig = z.infer<typeof LandingApiShowcaseSchema>;
|
|
343
|
+
export type LandingPricingCtaConfig = z.infer<typeof LandingPricingCtaSchema>;
|
|
344
|
+
|
|
249
345
|
export type ResolvedLandingContent = {
|
|
250
346
|
hero?: {
|
|
251
347
|
title?: string;
|
|
348
|
+
titleAccent?: string;
|
|
252
349
|
subtitle?: string;
|
|
253
350
|
description?: string;
|
|
351
|
+
badge?: string;
|
|
352
|
+
highlights?: string[];
|
|
353
|
+
codeExample?: NonNullable<LandingCodeExampleConfig>;
|
|
354
|
+
};
|
|
355
|
+
featuresSection?: {
|
|
356
|
+
label?: string;
|
|
357
|
+
title?: string;
|
|
358
|
+
description?: string;
|
|
254
359
|
};
|
|
255
360
|
features?: LandingFeatureConfig[];
|
|
256
361
|
primaryAction?: LandingLinkConfig;
|
|
257
362
|
secondaryAction?: LandingLinkConfig;
|
|
363
|
+
ticker?: LandingTickerConfig;
|
|
364
|
+
trust?: LandingTrustConfig;
|
|
365
|
+
apiShowcase?: LandingApiShowcaseConfig;
|
|
366
|
+
pricingCta?: LandingPricingCtaConfig;
|
|
258
367
|
showPoweredBy?: boolean;
|
|
259
368
|
};
|
|
260
369
|
|
|
@@ -27,22 +27,12 @@ export function applyAuthenticationHeaderNav<T extends ZudokuConfig>(
|
|
|
27
27
|
const authNavEnabled = config.__meta?.authenticationEnabled === true;
|
|
28
28
|
const navigation = config.header?.navigation ?? [];
|
|
29
29
|
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
if (filtered.length === navigation.length) {
|
|
33
|
-
return config;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
...config,
|
|
38
|
-
header: {
|
|
39
|
-
...config.header,
|
|
40
|
-
navigation: filtered,
|
|
41
|
-
},
|
|
42
|
-
};
|
|
30
|
+
if (authNavEnabled) {
|
|
31
|
+
return config;
|
|
43
32
|
}
|
|
44
33
|
|
|
45
|
-
|
|
34
|
+
const filtered = withoutLoginNavigation(navigation);
|
|
35
|
+
if (filtered.length === navigation.length) {
|
|
46
36
|
return config;
|
|
47
37
|
}
|
|
48
38
|
|
|
@@ -50,7 +40,7 @@ export function applyAuthenticationHeaderNav<T extends ZudokuConfig>(
|
|
|
50
40
|
...config,
|
|
51
41
|
header: {
|
|
52
42
|
...config.header,
|
|
53
|
-
navigation:
|
|
43
|
+
navigation: filtered,
|
|
54
44
|
},
|
|
55
45
|
};
|
|
56
46
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { HeaderNavigation } from "../../config/validators/HeaderNavigationSchema.js";
|
|
2
|
+
import { LOGIN_NAV_PATH } from "./auth-header-nav.js";
|
|
3
|
+
|
|
4
|
+
const AUTH_HEADER_PATHS = new Set([
|
|
5
|
+
LOGIN_NAV_PATH,
|
|
6
|
+
"/signin",
|
|
7
|
+
"/login",
|
|
8
|
+
"/signup",
|
|
9
|
+
"/register",
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
const PRICING_HEADER_PATHS = new Set(["/pricing"]);
|
|
13
|
+
|
|
14
|
+
export function withoutAuthHeaderNavigation(
|
|
15
|
+
navigation: HeaderNavigation,
|
|
16
|
+
): HeaderNavigation {
|
|
17
|
+
return navigation.filter(
|
|
18
|
+
(item) => !("to" in item) || !AUTH_HEADER_PATHS.has(item.to),
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function withoutPricingHeaderNavigation(
|
|
23
|
+
navigation: HeaderNavigation,
|
|
24
|
+
): HeaderNavigation {
|
|
25
|
+
return navigation.filter(
|
|
26
|
+
(item) => !("to" in item) || !PRICING_HEADER_PATHS.has(item.to),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function withoutMarketingCtaHeaderNavigation(
|
|
31
|
+
navigation: HeaderNavigation,
|
|
32
|
+
): HeaderNavigation {
|
|
33
|
+
return withoutPricingHeaderNavigation(
|
|
34
|
+
withoutAuthHeaderNavigation(navigation),
|
|
35
|
+
);
|
|
36
|
+
}
|