@lukoweb/apitogo 0.1.57 → 0.1.59

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 CHANGED
@@ -667,7 +667,7 @@ var init_file_exists = __esm({
667
667
 
668
668
  // src/config/validators/ModulesSchema.ts
669
669
  import { z as z3 } from "zod";
670
- var ModuleToggleSchema, DocsModuleSchema, LandingLinkSchema, LandingFeatureSchema, LandingCodeExampleSchema, LandingHeroSchema, LandingFeaturesSectionSchema, LandingTickerItemSchema, LandingTickerSchema, LandingTrustSchema, LandingApiShowcaseItemSchema, LandingApiShowcaseSchema, LandingPricingCtaSchema, LandingContentSchema, DashboardContentSchema, ProfileContentSchema, PlanTierSchema, PlansContentSchema, SubmodulePageFields, LandingModuleSchema, UserManagementModuleSchema, ApiKeysModuleSchema, PlanCatalogItemSchema, PlansModuleSchema, DashboardModuleSchema, UserPanelModuleSchema, ModulesSchema;
670
+ var ModuleToggleSchema, DocsModuleSchema, LandingLinkSchema, LandingFeatureSchema, LandingCodeExampleSchema, LandingHeroSchema, LandingFeaturesSectionSchema, LandingTrustSchema, LandingApiShowcaseItemSchema, LandingApiShowcaseSchema, LandingPricingCtaSchema, LandingFooterBrandSchema, LandingFooterColumnSchema, LandingFooterSocialSchema, LandingFooterSchema, LandingContentSchema, DashboardContentSchema, ProfileContentSchema, PlanTierSchema, PlansContentSchema, SubmodulePageFields, LandingModuleSchema, UserManagementModuleSchema, ApiKeysModuleSchema, PlanCatalogItemSchema, PlansModuleSchema, DashboardModuleSchema, UserPanelModuleSchema, ModulesSchema;
671
671
  var init_ModulesSchema = __esm({
672
672
  "src/config/validators/ModulesSchema.ts"() {
673
673
  ModuleToggleSchema = z3.object({
@@ -708,7 +708,9 @@ var init_ModulesSchema = __esm({
708
708
  }).optional();
709
709
  LandingHeroSchema = z3.object({
710
710
  title: z3.string().optional(),
711
- titleAccent: z3.string().optional().describe("Accent-colored word or phrase shown on a new line after the title."),
711
+ titleAccent: z3.string().optional().describe(
712
+ "Accent-colored word or phrase shown on a new line after the title."
713
+ ),
712
714
  subtitle: z3.string().optional(),
713
715
  description: z3.string().optional(),
714
716
  badge: z3.string().optional(),
@@ -720,15 +722,6 @@ var init_ModulesSchema = __esm({
720
722
  title: z3.string().optional(),
721
723
  description: z3.string().optional()
722
724
  }).optional();
723
- LandingTickerItemSchema = z3.object({
724
- label: z3.string(),
725
- value: z3.string(),
726
- change: z3.string().optional()
727
- });
728
- LandingTickerSchema = z3.object({
729
- enabled: z3.boolean().optional(),
730
- items: z3.array(LandingTickerItemSchema).optional()
731
- }).optional();
732
725
  LandingTrustSchema = z3.object({
733
726
  enabled: z3.boolean().optional(),
734
727
  title: z3.string().optional(),
@@ -757,16 +750,37 @@ var init_ModulesSchema = __esm({
757
750
  primaryAction: LandingLinkSchema.optional(),
758
751
  secondaryAction: LandingLinkSchema.optional()
759
752
  }).optional();
753
+ LandingFooterBrandSchema = z3.object({
754
+ name: z3.string().optional(),
755
+ tagline: z3.string().optional(),
756
+ logoSrc: z3.string().optional()
757
+ }).optional();
758
+ LandingFooterColumnSchema = z3.object({
759
+ title: z3.string(),
760
+ links: z3.array(LandingLinkSchema)
761
+ });
762
+ LandingFooterSocialSchema = z3.object({
763
+ icon: z3.string(),
764
+ href: z3.string(),
765
+ label: z3.string().optional()
766
+ });
767
+ LandingFooterSchema = z3.object({
768
+ enabled: z3.boolean().optional(),
769
+ brand: LandingFooterBrandSchema,
770
+ columns: z3.array(LandingFooterColumnSchema).optional(),
771
+ copyright: z3.string().optional(),
772
+ social: z3.array(LandingFooterSocialSchema).optional()
773
+ }).optional();
760
774
  LandingContentSchema = z3.object({
761
775
  hero: LandingHeroSchema,
762
776
  featuresSection: LandingFeaturesSectionSchema,
763
777
  features: z3.array(LandingFeatureSchema).optional(),
764
778
  primaryAction: LandingLinkSchema.optional(),
765
779
  secondaryAction: LandingLinkSchema.optional(),
766
- ticker: LandingTickerSchema,
767
780
  trust: LandingTrustSchema,
768
781
  apiShowcase: LandingApiShowcaseSchema,
769
782
  pricingCta: LandingPricingCtaSchema,
783
+ footer: LandingFooterSchema,
770
784
  showPoweredBy: z3.boolean().optional()
771
785
  }).optional();
772
786
  DashboardContentSchema = z3.object({
@@ -1071,21 +1085,16 @@ var init_default_landing_content = __esm({
1071
1085
  },
1072
1086
  primaryAction: { label: "Get your API key", href: "/register" },
1073
1087
  secondaryAction: { label: "Read the docs", href: "/introduction" },
1074
- ticker: {
1075
- items: [
1076
- { label: "GET", value: "/v1/items", change: "28ms" },
1077
- { label: "POST", value: "/v1/items", change: "34ms" },
1078
- { label: "GET", value: "/v1/users", change: "19ms" },
1079
- { label: "PATCH", value: "/v1/users/{id}", change: "41ms" },
1080
- { label: "GET", value: "/v1/health", change: "16ms" },
1081
- { label: "DELETE", value: "/v1/items/{id}", change: "22ms" },
1082
- { label: "GET", value: "/v1/search", change: "47ms" },
1083
- { label: "POST", value: "/v1/auth/token", change: "52ms" }
1084
- ]
1085
- },
1086
1088
  trust: {
1087
1089
  title: "Trusted by teams shipping production APIs",
1088
- logos: ["Acme Corp", "BuildFast", "DataFlow", "ShipKit", "DevStack", "LaunchPad"]
1090
+ logos: [
1091
+ "Acme Corp",
1092
+ "BuildFast",
1093
+ "DataFlow",
1094
+ "ShipKit",
1095
+ "DevStack",
1096
+ "LaunchPad"
1097
+ ]
1089
1098
  },
1090
1099
  featuresSection: {
1091
1100
  label: "// why us",
@@ -1253,6 +1262,7 @@ var init_resolve_modules = __esm({
1253
1262
  const siteTitle = config2.metadata?.applicationName ?? config2.site?.title ?? "APIToGo";
1254
1263
  if (options?.useDefaults === true) {
1255
1264
  const defaults = buildDefaultLandingContent(config2);
1265
+ const footer2 = mergeLandingSection(defaults.footer, content?.footer);
1256
1266
  return {
1257
1267
  hero: {
1258
1268
  ...defaults.hero,
@@ -1269,14 +1279,15 @@ var init_resolve_modules = __esm({
1269
1279
  features: content?.features ?? defaults.features,
1270
1280
  primaryAction: content?.primaryAction ?? defaults.primaryAction,
1271
1281
  secondaryAction: content?.secondaryAction ?? defaults.secondaryAction,
1272
- showPoweredBy: content?.showPoweredBy ?? defaults.showPoweredBy,
1273
- ticker: mergeLandingSection(defaults.ticker, content?.ticker),
1282
+ // A configured footer replaces the "Built with APIToGo" powered-by line.
1283
+ showPoweredBy: content?.showPoweredBy ?? (footer2 ? false : defaults.showPoweredBy),
1274
1284
  trust: mergeLandingSection(defaults.trust, content?.trust),
1275
1285
  apiShowcase: mergeLandingSection(
1276
1286
  defaults.apiShowcase,
1277
1287
  content?.apiShowcase
1278
1288
  ),
1279
- pricingCta: mergeLandingSection(defaults.pricingCta, content?.pricingCta)
1289
+ pricingCta: mergeLandingSection(defaults.pricingCta, content?.pricingCta),
1290
+ footer: footer2
1280
1291
  };
1281
1292
  }
1282
1293
  const hasContent = content !== void 0 && Object.keys(content).length > 0;
@@ -1286,6 +1297,7 @@ var init_resolve_modules = __esm({
1286
1297
  showPoweredBy: true
1287
1298
  };
1288
1299
  }
1300
+ const footer = content.footer ? mergeLandingSection(void 0, content.footer) : void 0;
1289
1301
  return {
1290
1302
  hero: {
1291
1303
  ...content.hero,
@@ -1295,11 +1307,12 @@ var init_resolve_modules = __esm({
1295
1307
  features: content.features,
1296
1308
  primaryAction: content.primaryAction,
1297
1309
  secondaryAction: content.secondaryAction,
1298
- showPoweredBy: content.showPoweredBy ?? true,
1299
- ticker: content.ticker ? mergeLandingSection(void 0, content.ticker) : void 0,
1310
+ // A configured footer replaces the "Built with APIToGo" powered-by line.
1311
+ showPoweredBy: content.showPoweredBy ?? !footer,
1300
1312
  trust: content.trust ? mergeLandingSection(void 0, content.trust) : void 0,
1301
1313
  apiShowcase: content.apiShowcase ? mergeLandingSection(void 0, content.apiShowcase) : void 0,
1302
- pricingCta: content.pricingCta ? mergeLandingSection(void 0, content.pricingCta) : void 0
1314
+ pricingCta: content.pricingCta ? mergeLandingSection(void 0, content.pricingCta) : void 0,
1315
+ footer
1303
1316
  };
1304
1317
  };
1305
1318
  DEFAULT_DASHBOARD_QUICK_LINKS = [
@@ -1334,7 +1347,7 @@ var init_resolve_modules = __esm({
1334
1347
  const userManagementConfig = userPanelModule?.userManagement;
1335
1348
  const apiKeysModuleConfig = userPanelModule?.apiKeys;
1336
1349
  const plansConfig = userPanelModule?.plans;
1337
- const dashboardEnabled = dashboardConfig?.enabled ?? (explicit ? false : false);
1350
+ const dashboardEnabled = dashboardConfig?.enabled ?? false;
1338
1351
  const userManagementEnabled = explicit ? userManagementConfig?.enabled ?? !!config2.authentication : !!config2.authentication;
1339
1352
  const apiKeysEnabled = explicit ? apiKeysModuleConfig?.enabled ?? false : false;
1340
1353
  const billingEnabled = config2.__meta?.pricingEnabled === true || plansConfig?.enabled === true;
@@ -5214,7 +5227,7 @@ import {
5214
5227
  // package.json
5215
5228
  var package_default = {
5216
5229
  name: "@lukoweb/apitogo",
5217
- version: "0.1.57",
5230
+ version: "0.1.59",
5218
5231
  type: "module",
5219
5232
  sideEffects: [
5220
5233
  "**/*.css",
@@ -8913,7 +8926,10 @@ var viteModulesPlugin = () => {
8913
8926
  const config2 = getCurrentConfig();
8914
8927
  const { landing, userPanel } = config2.__resolvedModules;
8915
8928
  if (config2.__meta.mode === "standalone") {
8916
- return `export const configuredModulesPlugins = [];`;
8929
+ return [
8930
+ `export const configuredModulesPlugins = [];`,
8931
+ `export const configuredModulesSlots = {};`
8932
+ ].join("\n");
8917
8933
  }
8918
8934
  const landingImport = getLandingModuleImport(
8919
8935
  config2.__meta.moduleDir,
@@ -8975,6 +8991,7 @@ var viteModulesPlugin = () => {
8975
8991
  );
8976
8992
  }
8977
8993
  const configuredPlugins = [];
8994
+ let hasLandingFooterSlot = false;
8978
8995
  if (landing.enabled) {
8979
8996
  code.push(`import { landingModule } from "${landingImport}";`);
8980
8997
  code.push(
@@ -8984,7 +9001,17 @@ var viteModulesPlugin = () => {
8984
9001
  `});`
8985
9002
  );
8986
9003
  configuredPlugins.push("...(landing ? [landing] : [])");
9004
+ if (landing.content.footer) {
9005
+ code.push(
9006
+ `import { createLandingFooterSlot } from "${landingImport}";`,
9007
+ `const landingFooterSlot = createLandingFooterSlot(${JSON.stringify(landing.content.footer)});`
9008
+ );
9009
+ hasLandingFooterSlot = true;
9010
+ }
8987
9011
  }
9012
+ code.push(
9013
+ hasLandingFooterSlot ? `export const configuredModulesSlots = landingFooterSlot ? { footer: landingFooterSlot } : {};` : `export const configuredModulesSlots = {};`
9014
+ );
8988
9015
  if (userPanel.enabled) {
8989
9016
  code.push(`import { userPanelModule } from "${userPanelImport}";`);
8990
9017
  code.push(
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import type { z } from "zod";
2
2
  import { LandingContentSchema } from "./validators/ModulesSchema.js";
3
3
  export declare const LandingManifestContentSchema: z.ZodOptional<z.ZodObject<{
4
4
  hero: z.ZodOptional<z.ZodObject<{
@@ -32,14 +32,6 @@ export declare const LandingManifestContentSchema: z.ZodOptional<z.ZodObject<{
32
32
  label: z.ZodString;
33
33
  href: z.ZodString;
34
34
  }, z.core.$strip>>;
35
- ticker: z.ZodOptional<z.ZodObject<{
36
- enabled: z.ZodOptional<z.ZodBoolean>;
37
- items: z.ZodOptional<z.ZodArray<z.ZodObject<{
38
- label: z.ZodString;
39
- value: z.ZodString;
40
- change: z.ZodOptional<z.ZodString>;
41
- }, z.core.$strip>>>;
42
- }, z.core.$strip>>;
43
35
  trust: z.ZodOptional<z.ZodObject<{
44
36
  enabled: z.ZodOptional<z.ZodBoolean>;
45
37
  title: z.ZodOptional<z.ZodString>;
@@ -73,6 +65,27 @@ export declare const LandingManifestContentSchema: z.ZodOptional<z.ZodObject<{
73
65
  href: z.ZodString;
74
66
  }, z.core.$strip>>;
75
67
  }, z.core.$strip>>;
68
+ footer: z.ZodOptional<z.ZodObject<{
69
+ enabled: z.ZodOptional<z.ZodBoolean>;
70
+ brand: z.ZodOptional<z.ZodObject<{
71
+ name: z.ZodOptional<z.ZodString>;
72
+ tagline: z.ZodOptional<z.ZodString>;
73
+ logoSrc: z.ZodOptional<z.ZodString>;
74
+ }, z.core.$strip>>;
75
+ columns: z.ZodOptional<z.ZodArray<z.ZodObject<{
76
+ title: z.ZodString;
77
+ links: z.ZodArray<z.ZodObject<{
78
+ label: z.ZodString;
79
+ href: z.ZodString;
80
+ }, z.core.$strip>>;
81
+ }, z.core.$strip>>>;
82
+ copyright: z.ZodOptional<z.ZodString>;
83
+ social: z.ZodOptional<z.ZodArray<z.ZodObject<{
84
+ icon: z.ZodString;
85
+ href: z.ZodString;
86
+ label: z.ZodOptional<z.ZodString>;
87
+ }, z.core.$strip>>>;
88
+ }, z.core.$strip>>;
76
89
  showPoweredBy: z.ZodOptional<z.ZodBoolean>;
77
90
  }, z.core.$strip>>;
78
91
  export type LandingManifestContent = z.infer<typeof LandingContentSchema>;
@@ -120,14 +120,6 @@ export declare const DevPortalLocalManifestSchema: z.ZodObject<{
120
120
  label: z.ZodString;
121
121
  href: z.ZodString;
122
122
  }, z.core.$strip>>;
123
- ticker: z.ZodOptional<z.ZodObject<{
124
- enabled: z.ZodOptional<z.ZodBoolean>;
125
- items: z.ZodOptional<z.ZodArray<z.ZodObject<{
126
- label: z.ZodString;
127
- value: z.ZodString;
128
- change: z.ZodOptional<z.ZodString>;
129
- }, z.core.$strip>>>;
130
- }, z.core.$strip>>;
131
123
  trust: z.ZodOptional<z.ZodObject<{
132
124
  enabled: z.ZodOptional<z.ZodBoolean>;
133
125
  title: z.ZodOptional<z.ZodString>;
@@ -161,6 +153,27 @@ export declare const DevPortalLocalManifestSchema: z.ZodObject<{
161
153
  href: z.ZodString;
162
154
  }, z.core.$strip>>;
163
155
  }, z.core.$strip>>;
156
+ footer: z.ZodOptional<z.ZodObject<{
157
+ enabled: z.ZodOptional<z.ZodBoolean>;
158
+ brand: z.ZodOptional<z.ZodObject<{
159
+ name: z.ZodOptional<z.ZodString>;
160
+ tagline: z.ZodOptional<z.ZodString>;
161
+ logoSrc: z.ZodOptional<z.ZodString>;
162
+ }, z.core.$strip>>;
163
+ columns: z.ZodOptional<z.ZodArray<z.ZodObject<{
164
+ title: z.ZodString;
165
+ links: z.ZodArray<z.ZodObject<{
166
+ label: z.ZodString;
167
+ href: z.ZodString;
168
+ }, z.core.$strip>>;
169
+ }, z.core.$strip>>>;
170
+ copyright: z.ZodOptional<z.ZodString>;
171
+ social: z.ZodOptional<z.ZodArray<z.ZodObject<{
172
+ icon: z.ZodString;
173
+ href: z.ZodString;
174
+ label: z.ZodOptional<z.ZodString>;
175
+ }, z.core.$strip>>>;
176
+ }, z.core.$strip>>;
164
177
  showPoweredBy: z.ZodOptional<z.ZodBoolean>;
165
178
  }, z.core.$strip>>;
166
179
  }, z.core.$strip>>;
@@ -12,7 +12,7 @@ export declare const getEffectiveRedirects: (config: ZudokuConfig, resolved: Res
12
12
  from: string;
13
13
  to: string;
14
14
  }[];
15
- export declare const getEffectiveDocsConfig: (config: ZudokuConfig, resolved: ResolvedModulesConfig) => {
15
+ export declare const getEffectiveDocsConfig: (_config: ZudokuConfig, resolved: ResolvedModulesConfig) => {
16
16
  files: string | string[];
17
17
  publishMarkdown: boolean;
18
18
  defaultOptions: {
@@ -52,19 +52,6 @@ export declare const LandingFeaturesSectionSchema: z.ZodOptional<z.ZodObject<{
52
52
  title: z.ZodOptional<z.ZodString>;
53
53
  description: z.ZodOptional<z.ZodString>;
54
54
  }, z.core.$strip>>;
55
- export declare const LandingTickerItemSchema: z.ZodObject<{
56
- label: z.ZodString;
57
- value: z.ZodString;
58
- change: z.ZodOptional<z.ZodString>;
59
- }, z.core.$strip>;
60
- export declare const LandingTickerSchema: z.ZodOptional<z.ZodObject<{
61
- enabled: z.ZodOptional<z.ZodBoolean>;
62
- items: z.ZodOptional<z.ZodArray<z.ZodObject<{
63
- label: z.ZodString;
64
- value: z.ZodString;
65
- change: z.ZodOptional<z.ZodString>;
66
- }, z.core.$strip>>>;
67
- }, z.core.$strip>>;
68
55
  export declare const LandingTrustSchema: z.ZodOptional<z.ZodObject<{
69
56
  enabled: z.ZodOptional<z.ZodBoolean>;
70
57
  title: z.ZodOptional<z.ZodString>;
@@ -105,6 +92,44 @@ export declare const LandingPricingCtaSchema: z.ZodOptional<z.ZodObject<{
105
92
  href: z.ZodString;
106
93
  }, z.core.$strip>>;
107
94
  }, z.core.$strip>>;
95
+ export declare const LandingFooterBrandSchema: z.ZodOptional<z.ZodObject<{
96
+ name: z.ZodOptional<z.ZodString>;
97
+ tagline: z.ZodOptional<z.ZodString>;
98
+ logoSrc: z.ZodOptional<z.ZodString>;
99
+ }, z.core.$strip>>;
100
+ export declare const LandingFooterColumnSchema: z.ZodObject<{
101
+ title: z.ZodString;
102
+ links: z.ZodArray<z.ZodObject<{
103
+ label: z.ZodString;
104
+ href: z.ZodString;
105
+ }, z.core.$strip>>;
106
+ }, z.core.$strip>;
107
+ export declare const LandingFooterSocialSchema: z.ZodObject<{
108
+ icon: z.ZodString;
109
+ href: z.ZodString;
110
+ label: z.ZodOptional<z.ZodString>;
111
+ }, z.core.$strip>;
112
+ export declare const LandingFooterSchema: z.ZodOptional<z.ZodObject<{
113
+ enabled: z.ZodOptional<z.ZodBoolean>;
114
+ brand: z.ZodOptional<z.ZodObject<{
115
+ name: z.ZodOptional<z.ZodString>;
116
+ tagline: z.ZodOptional<z.ZodString>;
117
+ logoSrc: z.ZodOptional<z.ZodString>;
118
+ }, z.core.$strip>>;
119
+ columns: z.ZodOptional<z.ZodArray<z.ZodObject<{
120
+ title: z.ZodString;
121
+ links: z.ZodArray<z.ZodObject<{
122
+ label: z.ZodString;
123
+ href: z.ZodString;
124
+ }, z.core.$strip>>;
125
+ }, z.core.$strip>>>;
126
+ copyright: z.ZodOptional<z.ZodString>;
127
+ social: z.ZodOptional<z.ZodArray<z.ZodObject<{
128
+ icon: z.ZodString;
129
+ href: z.ZodString;
130
+ label: z.ZodOptional<z.ZodString>;
131
+ }, z.core.$strip>>>;
132
+ }, z.core.$strip>>;
108
133
  export declare const LandingContentSchema: z.ZodOptional<z.ZodObject<{
109
134
  hero: z.ZodOptional<z.ZodObject<{
110
135
  title: z.ZodOptional<z.ZodString>;
@@ -137,14 +162,6 @@ export declare const LandingContentSchema: z.ZodOptional<z.ZodObject<{
137
162
  label: z.ZodString;
138
163
  href: z.ZodString;
139
164
  }, z.core.$strip>>;
140
- ticker: z.ZodOptional<z.ZodObject<{
141
- enabled: z.ZodOptional<z.ZodBoolean>;
142
- items: z.ZodOptional<z.ZodArray<z.ZodObject<{
143
- label: z.ZodString;
144
- value: z.ZodString;
145
- change: z.ZodOptional<z.ZodString>;
146
- }, z.core.$strip>>>;
147
- }, z.core.$strip>>;
148
165
  trust: z.ZodOptional<z.ZodObject<{
149
166
  enabled: z.ZodOptional<z.ZodBoolean>;
150
167
  title: z.ZodOptional<z.ZodString>;
@@ -178,6 +195,27 @@ export declare const LandingContentSchema: z.ZodOptional<z.ZodObject<{
178
195
  href: z.ZodString;
179
196
  }, z.core.$strip>>;
180
197
  }, z.core.$strip>>;
198
+ footer: z.ZodOptional<z.ZodObject<{
199
+ enabled: z.ZodOptional<z.ZodBoolean>;
200
+ brand: z.ZodOptional<z.ZodObject<{
201
+ name: z.ZodOptional<z.ZodString>;
202
+ tagline: z.ZodOptional<z.ZodString>;
203
+ logoSrc: z.ZodOptional<z.ZodString>;
204
+ }, z.core.$strip>>;
205
+ columns: z.ZodOptional<z.ZodArray<z.ZodObject<{
206
+ title: z.ZodString;
207
+ links: z.ZodArray<z.ZodObject<{
208
+ label: z.ZodString;
209
+ href: z.ZodString;
210
+ }, z.core.$strip>>;
211
+ }, z.core.$strip>>>;
212
+ copyright: z.ZodOptional<z.ZodString>;
213
+ social: z.ZodOptional<z.ZodArray<z.ZodObject<{
214
+ icon: z.ZodString;
215
+ href: z.ZodString;
216
+ label: z.ZodOptional<z.ZodString>;
217
+ }, z.core.$strip>>>;
218
+ }, z.core.$strip>>;
181
219
  showPoweredBy: z.ZodOptional<z.ZodBoolean>;
182
220
  }, z.core.$strip>>;
183
221
  export declare const DashboardContentSchema: z.ZodOptional<z.ZodObject<{
@@ -253,14 +291,6 @@ export declare const LandingModuleSchema: z.ZodObject<{
253
291
  label: z.ZodString;
254
292
  href: z.ZodString;
255
293
  }, z.core.$strip>>;
256
- ticker: z.ZodOptional<z.ZodObject<{
257
- enabled: z.ZodOptional<z.ZodBoolean>;
258
- items: z.ZodOptional<z.ZodArray<z.ZodObject<{
259
- label: z.ZodString;
260
- value: z.ZodString;
261
- change: z.ZodOptional<z.ZodString>;
262
- }, z.core.$strip>>>;
263
- }, z.core.$strip>>;
264
294
  trust: z.ZodOptional<z.ZodObject<{
265
295
  enabled: z.ZodOptional<z.ZodBoolean>;
266
296
  title: z.ZodOptional<z.ZodString>;
@@ -294,6 +324,27 @@ export declare const LandingModuleSchema: z.ZodObject<{
294
324
  href: z.ZodString;
295
325
  }, z.core.$strip>>;
296
326
  }, z.core.$strip>>;
327
+ footer: z.ZodOptional<z.ZodObject<{
328
+ enabled: z.ZodOptional<z.ZodBoolean>;
329
+ brand: z.ZodOptional<z.ZodObject<{
330
+ name: z.ZodOptional<z.ZodString>;
331
+ tagline: z.ZodOptional<z.ZodString>;
332
+ logoSrc: z.ZodOptional<z.ZodString>;
333
+ }, z.core.$strip>>;
334
+ columns: z.ZodOptional<z.ZodArray<z.ZodObject<{
335
+ title: z.ZodString;
336
+ links: z.ZodArray<z.ZodObject<{
337
+ label: z.ZodString;
338
+ href: z.ZodString;
339
+ }, z.core.$strip>>;
340
+ }, z.core.$strip>>>;
341
+ copyright: z.ZodOptional<z.ZodString>;
342
+ social: z.ZodOptional<z.ZodArray<z.ZodObject<{
343
+ icon: z.ZodString;
344
+ href: z.ZodString;
345
+ label: z.ZodOptional<z.ZodString>;
346
+ }, z.core.$strip>>>;
347
+ }, z.core.$strip>>;
297
348
  showPoweredBy: z.ZodOptional<z.ZodBoolean>;
298
349
  }, z.core.$strip>>;
299
350
  useDefaults: z.ZodOptional<z.ZodBoolean>;
@@ -490,14 +541,6 @@ export declare const ModulesSchema: z.ZodOptional<z.ZodObject<{
490
541
  label: z.ZodString;
491
542
  href: z.ZodString;
492
543
  }, z.core.$strip>>;
493
- ticker: z.ZodOptional<z.ZodObject<{
494
- enabled: z.ZodOptional<z.ZodBoolean>;
495
- items: z.ZodOptional<z.ZodArray<z.ZodObject<{
496
- label: z.ZodString;
497
- value: z.ZodString;
498
- change: z.ZodOptional<z.ZodString>;
499
- }, z.core.$strip>>>;
500
- }, z.core.$strip>>;
501
544
  trust: z.ZodOptional<z.ZodObject<{
502
545
  enabled: z.ZodOptional<z.ZodBoolean>;
503
546
  title: z.ZodOptional<z.ZodString>;
@@ -531,6 +574,27 @@ export declare const ModulesSchema: z.ZodOptional<z.ZodObject<{
531
574
  href: z.ZodString;
532
575
  }, z.core.$strip>>;
533
576
  }, z.core.$strip>>;
577
+ footer: z.ZodOptional<z.ZodObject<{
578
+ enabled: z.ZodOptional<z.ZodBoolean>;
579
+ brand: z.ZodOptional<z.ZodObject<{
580
+ name: z.ZodOptional<z.ZodString>;
581
+ tagline: z.ZodOptional<z.ZodString>;
582
+ logoSrc: z.ZodOptional<z.ZodString>;
583
+ }, z.core.$strip>>;
584
+ columns: z.ZodOptional<z.ZodArray<z.ZodObject<{
585
+ title: z.ZodString;
586
+ links: z.ZodArray<z.ZodObject<{
587
+ label: z.ZodString;
588
+ href: z.ZodString;
589
+ }, z.core.$strip>>;
590
+ }, z.core.$strip>>>;
591
+ copyright: z.ZodOptional<z.ZodString>;
592
+ social: z.ZodOptional<z.ZodArray<z.ZodObject<{
593
+ icon: z.ZodString;
594
+ href: z.ZodString;
595
+ label: z.ZodOptional<z.ZodString>;
596
+ }, z.core.$strip>>>;
597
+ }, z.core.$strip>>;
534
598
  showPoweredBy: z.ZodOptional<z.ZodBoolean>;
535
599
  }, z.core.$strip>>;
536
600
  useDefaults: z.ZodOptional<z.ZodBoolean>;
@@ -621,12 +685,14 @@ export type ProfileContentConfig = z.infer<typeof ProfileContentSchema>;
621
685
  export type PlansContentConfig = z.infer<typeof PlansContentSchema>;
622
686
  export type PlanTierConfig = z.infer<typeof PlanTierSchema>;
623
687
  export type LandingCodeExampleConfig = z.infer<typeof LandingCodeExampleSchema>;
624
- export type LandingTickerItemConfig = z.infer<typeof LandingTickerItemSchema>;
625
- export type LandingTickerConfig = z.infer<typeof LandingTickerSchema>;
626
688
  export type LandingTrustConfig = z.infer<typeof LandingTrustSchema>;
627
689
  export type LandingApiShowcaseItemConfig = z.infer<typeof LandingApiShowcaseItemSchema>;
628
690
  export type LandingApiShowcaseConfig = z.infer<typeof LandingApiShowcaseSchema>;
629
691
  export type LandingPricingCtaConfig = z.infer<typeof LandingPricingCtaSchema>;
692
+ export type LandingFooterBrandConfig = z.infer<typeof LandingFooterBrandSchema>;
693
+ export type LandingFooterColumnConfig = z.infer<typeof LandingFooterColumnSchema>;
694
+ export type LandingFooterSocialConfig = z.infer<typeof LandingFooterSocialSchema>;
695
+ export type LandingFooterConfig = z.infer<typeof LandingFooterSchema>;
630
696
  export type ResolvedLandingContent = {
631
697
  hero?: {
632
698
  title?: string;
@@ -645,10 +711,10 @@ export type ResolvedLandingContent = {
645
711
  features?: LandingFeatureConfig[];
646
712
  primaryAction?: LandingLinkConfig;
647
713
  secondaryAction?: LandingLinkConfig;
648
- ticker?: LandingTickerConfig;
649
714
  trust?: LandingTrustConfig;
650
715
  apiShowcase?: LandingApiShowcaseConfig;
651
716
  pricingCta?: LandingPricingCtaConfig;
717
+ footer?: LandingFooterConfig;
652
718
  showPoweredBy?: boolean;
653
719
  };
654
720
  export type ResolvedLandingModule = {
@@ -6919,14 +6919,6 @@ export declare const ZudokuConfig: z.ZodObject<{
6919
6919
  label: z.ZodString;
6920
6920
  href: z.ZodString;
6921
6921
  }, z.core.$strip>>;
6922
- ticker: z.ZodOptional<z.ZodObject<{
6923
- enabled: z.ZodOptional<z.ZodBoolean>;
6924
- items: z.ZodOptional<z.ZodArray<z.ZodObject<{
6925
- label: z.ZodString;
6926
- value: z.ZodString;
6927
- change: z.ZodOptional<z.ZodString>;
6928
- }, z.core.$strip>>>;
6929
- }, z.core.$strip>>;
6930
6922
  trust: z.ZodOptional<z.ZodObject<{
6931
6923
  enabled: z.ZodOptional<z.ZodBoolean>;
6932
6924
  title: z.ZodOptional<z.ZodString>;
@@ -6960,6 +6952,27 @@ export declare const ZudokuConfig: z.ZodObject<{
6960
6952
  href: z.ZodString;
6961
6953
  }, z.core.$strip>>;
6962
6954
  }, z.core.$strip>>;
6955
+ footer: z.ZodOptional<z.ZodObject<{
6956
+ enabled: z.ZodOptional<z.ZodBoolean>;
6957
+ brand: z.ZodOptional<z.ZodObject<{
6958
+ name: z.ZodOptional<z.ZodString>;
6959
+ tagline: z.ZodOptional<z.ZodString>;
6960
+ logoSrc: z.ZodOptional<z.ZodString>;
6961
+ }, z.core.$strip>>;
6962
+ columns: z.ZodOptional<z.ZodArray<z.ZodObject<{
6963
+ title: z.ZodString;
6964
+ links: z.ZodArray<z.ZodObject<{
6965
+ label: z.ZodString;
6966
+ href: z.ZodString;
6967
+ }, z.core.$strip>>;
6968
+ }, z.core.$strip>>>;
6969
+ copyright: z.ZodOptional<z.ZodString>;
6970
+ social: z.ZodOptional<z.ZodArray<z.ZodObject<{
6971
+ icon: z.ZodString;
6972
+ href: z.ZodString;
6973
+ label: z.ZodOptional<z.ZodString>;
6974
+ }, z.core.$strip>>>;
6975
+ }, z.core.$strip>>;
6963
6976
  showPoweredBy: z.ZodOptional<z.ZodBoolean>;
6964
6977
  }, z.core.$strip>>;
6965
6978
  useDefaults: z.ZodOptional<z.ZodBoolean>;
@@ -1,7 +1,7 @@
1
1
  import { type ReactNode } from "react";
2
2
  import { type SlotType } from "./context/SlotProvider.js";
3
3
  export type CustomSlotNames = never;
4
- type PredefinedSlotNames = "api-keys-list-page" | "api-keys-list-page-before-keys" | "footer-after" | "footer-before" | "head-navigation-end" | "head-navigation-start" | "layout-after-head" | "layout-before-head" | "top-navigation-after" | "top-navigation-before" | "top-navigation-side" | "content-before" | "content-after" | "navigation-after" | "navigation-before";
4
+ type PredefinedSlotNames = "api-keys-list-page" | "api-keys-list-page-before-keys" | "footer" | "footer-after" | "footer-before" | "head-navigation-end" | "head-navigation-start" | "layout-after-head" | "layout-before-head" | "top-navigation-after" | "top-navigation-before" | "top-navigation-side" | "content-before" | "content-after" | "navigation-after" | "navigation-before";
5
5
  export type SlotName = PredefinedSlotNames | CustomSlotNames;
6
6
  export declare const Slot: {
7
7
  Source: ({ name, children, type, }: {
@@ -562,14 +562,6 @@ export interface _Schema21 {
562
562
  }[]
563
563
  primaryAction?: _Schema23
564
564
  secondaryAction?: _Schema23
565
- ticker?: {
566
- enabled?: boolean
567
- items?: {
568
- label: string
569
- value: string
570
- change?: string
571
- }[]
572
- }
573
565
  trust?: {
574
566
  enabled?: boolean
575
567
  title?: string
@@ -597,6 +589,24 @@ export interface _Schema21 {
597
589
  primaryAction?: _Schema23
598
590
  secondaryAction?: _Schema23
599
591
  }
592
+ footer?: {
593
+ enabled?: boolean
594
+ brand?: {
595
+ name?: string
596
+ tagline?: string
597
+ logoSrc?: string
598
+ }
599
+ columns?: {
600
+ title: string
601
+ links: _Schema23[]
602
+ }[]
603
+ copyright?: string
604
+ social?: {
605
+ icon: string
606
+ href: string
607
+ label?: string
608
+ }[]
609
+ }
600
610
  showPoweredBy?: boolean
601
611
  }
602
612
  export interface _Schema23 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lukoweb/apitogo",
3
- "version": "0.1.57",
3
+ "version": "0.1.59",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "**/*.css",
package/src/app/main.tsx CHANGED
@@ -10,7 +10,10 @@ import {
10
10
  import { configuredAuthProvider } from "virtual:zudoku-auth";
11
11
  import { configuredCustomPagesPlugin } from "virtual:zudoku-custom-pages-plugin";
12
12
  import { configuredDocsPlugin } from "virtual:zudoku-docs-plugin";
13
- import { configuredModulesPlugins } from "virtual:zudoku-modules-plugin";
13
+ import {
14
+ configuredModulesPlugins,
15
+ configuredModulesSlots,
16
+ } from "virtual:zudoku-modules-plugin";
14
17
  import "virtual:zudoku-theme.css";
15
18
  import {
16
19
  configuredHeaderNavigation,
@@ -60,7 +63,7 @@ export const convertApitogoConfigToOptions = (
60
63
  showPoweredBy: ZuploEnv.buildConfig?.entitlements.devPortalZuploBranding,
61
64
  logo: config.site?.logo,
62
65
  },
63
- slots: config.slots,
66
+ slots: { ...configuredModulesSlots, ...config.slots },
64
67
  metadata: {
65
68
  favicon: "https://cdn.zudoku.dev/logos/favicon.svg",
66
69
  applicationName: siteTitle,
@@ -27,21 +27,16 @@ export const DEFAULT_LANDING_CONTENT: ResolvedLandingContent = {
27
27
  },
28
28
  primaryAction: { label: "Get your API key", href: "/register" },
29
29
  secondaryAction: { label: "Read the docs", href: "/introduction" },
30
- ticker: {
31
- items: [
32
- { label: "GET", value: "/v1/items", change: "28ms" },
33
- { label: "POST", value: "/v1/items", change: "34ms" },
34
- { label: "GET", value: "/v1/users", change: "19ms" },
35
- { label: "PATCH", value: "/v1/users/{id}", change: "41ms" },
36
- { label: "GET", value: "/v1/health", change: "16ms" },
37
- { label: "DELETE", value: "/v1/items/{id}", change: "22ms" },
38
- { label: "GET", value: "/v1/search", change: "47ms" },
39
- { label: "POST", value: "/v1/auth/token", change: "52ms" },
40
- ],
41
- },
42
30
  trust: {
43
31
  title: "Trusted by teams shipping production APIs",
44
- logos: ["Acme Corp", "BuildFast", "DataFlow", "ShipKit", "DevStack", "LaunchPad"],
32
+ logos: [
33
+ "Acme Corp",
34
+ "BuildFast",
35
+ "DataFlow",
36
+ "ShipKit",
37
+ "DevStack",
38
+ "LaunchPad",
39
+ ],
45
40
  },
46
41
  featuresSection: {
47
42
  label: "// why us",
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import type { z } from "zod";
2
2
  import { LandingContentSchema } from "./validators/ModulesSchema.js";
3
3
 
4
4
  export const LandingManifestContentSchema = LandingContentSchema;
@@ -100,7 +100,16 @@ export function extractApiShowcaseItemsFromOpenApi(
100
100
 
101
101
  export function enrichResolvedLandingApiShowcase(
102
102
  config: ZudokuConfig,
103
- resolved: { landing: { content: { apiShowcase?: { autoFromOpenApi?: boolean; items?: LandingApiShowcaseItemConfig[] } } } },
103
+ resolved: {
104
+ landing: {
105
+ content: {
106
+ apiShowcase?: {
107
+ autoFromOpenApi?: boolean;
108
+ items?: LandingApiShowcaseItemConfig[];
109
+ };
110
+ };
111
+ };
112
+ },
104
113
  rootDir: string,
105
114
  ): void {
106
115
  const showcase = resolved.landing.content.apiShowcase;
@@ -261,13 +261,6 @@ export function mergeLandingContent(
261
261
  merged.features = Array.from(byTitle.values());
262
262
  }
263
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
264
  if (patch.trust) {
272
265
  merged.trust = { ...existing.trust, ...patch.trust };
273
266
  if (patch.trust.logos) {
@@ -294,7 +287,10 @@ export function mergeLandingContent(
294
287
  }
295
288
 
296
289
  if (patch.primaryAction) {
297
- merged.primaryAction = { ...existing.primaryAction, ...patch.primaryAction };
290
+ merged.primaryAction = {
291
+ ...existing.primaryAction,
292
+ ...patch.primaryAction,
293
+ };
298
294
  }
299
295
  if (patch.secondaryAction) {
300
296
  merged.secondaryAction = {
@@ -96,6 +96,7 @@ export const resolveLandingContent = (
96
96
 
97
97
  if (options?.useDefaults === true) {
98
98
  const defaults = buildDefaultLandingContent(config);
99
+ const footer = mergeLandingSection(defaults.footer, content?.footer);
99
100
 
100
101
  return {
101
102
  hero: {
@@ -118,19 +119,20 @@ export const resolveLandingContent = (
118
119
  features: content?.features ?? defaults.features,
119
120
  primaryAction: content?.primaryAction ?? defaults.primaryAction,
120
121
  secondaryAction: content?.secondaryAction ?? defaults.secondaryAction,
121
- showPoweredBy: content?.showPoweredBy ?? defaults.showPoweredBy,
122
- ticker: mergeLandingSection(defaults.ticker, content?.ticker),
122
+ // A configured footer replaces the "Built with APIToGo" powered-by line.
123
+ showPoweredBy:
124
+ content?.showPoweredBy ?? (footer ? false : defaults.showPoweredBy),
123
125
  trust: mergeLandingSection(defaults.trust, content?.trust),
124
126
  apiShowcase: mergeLandingSection(
125
127
  defaults.apiShowcase,
126
128
  content?.apiShowcase,
127
129
  ),
128
130
  pricingCta: mergeLandingSection(defaults.pricingCta, content?.pricingCta),
131
+ footer,
129
132
  };
130
133
  }
131
134
 
132
- const hasContent =
133
- content !== undefined && Object.keys(content).length > 0;
135
+ const hasContent = content !== undefined && Object.keys(content).length > 0;
134
136
 
135
137
  if (!hasContent) {
136
138
  return {
@@ -139,6 +141,10 @@ export const resolveLandingContent = (
139
141
  };
140
142
  }
141
143
 
144
+ const footer = content.footer
145
+ ? mergeLandingSection(undefined, content.footer)
146
+ : undefined;
147
+
142
148
  return {
143
149
  hero: {
144
150
  ...content.hero,
@@ -150,10 +156,8 @@ export const resolveLandingContent = (
150
156
  features: content.features,
151
157
  primaryAction: content.primaryAction,
152
158
  secondaryAction: content.secondaryAction,
153
- showPoweredBy: content.showPoweredBy ?? true,
154
- ticker: content.ticker
155
- ? mergeLandingSection(undefined, content.ticker)
156
- : undefined,
159
+ // A configured footer replaces the "Built with APIToGo" powered-by line.
160
+ showPoweredBy: content.showPoweredBy ?? !footer,
157
161
  trust: content.trust
158
162
  ? mergeLandingSection(undefined, content.trust)
159
163
  : undefined,
@@ -163,6 +167,7 @@ export const resolveLandingContent = (
163
167
  pricingCta: content.pricingCta
164
168
  ? mergeLandingSection(undefined, content.pricingCta)
165
169
  : undefined,
170
+ footer,
166
171
  };
167
172
  };
168
173
 
@@ -220,8 +225,7 @@ export const resolveModulesConfig = (
220
225
  const apiKeysModuleConfig = userPanelModule?.apiKeys;
221
226
  const plansConfig = userPanelModule?.plans;
222
227
 
223
- const dashboardEnabled =
224
- dashboardConfig?.enabled ?? (explicit ? false : false);
228
+ const dashboardEnabled = dashboardConfig?.enabled ?? false;
225
229
 
226
230
  const userManagementEnabled = explicit
227
231
  ? (userManagementConfig?.enabled ?? !!config.authentication)
@@ -327,7 +331,7 @@ const normalizePath = (path: string) => {
327
331
  };
328
332
 
329
333
  export const getEffectiveDocsConfig = (
330
- config: ZudokuConfig,
334
+ _config: ZudokuConfig,
331
335
  resolved: ResolvedModulesConfig,
332
336
  ) => {
333
337
  if (!resolved.docs.enabled) {
@@ -61,7 +61,9 @@ export const LandingHeroSchema = z
61
61
  titleAccent: z
62
62
  .string()
63
63
  .optional()
64
- .describe("Accent-colored word or phrase shown on a new line after the title."),
64
+ .describe(
65
+ "Accent-colored word or phrase shown on a new line after the title.",
66
+ ),
65
67
  subtitle: z.string().optional(),
66
68
  description: z.string().optional(),
67
69
  badge: z.string().optional(),
@@ -78,19 +80,6 @@ export const LandingFeaturesSectionSchema = z
78
80
  })
79
81
  .optional();
80
82
 
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
83
  export const LandingTrustSchema = z
95
84
  .object({
96
85
  enabled: z.boolean().optional(),
@@ -129,6 +118,35 @@ export const LandingPricingCtaSchema = z
129
118
  })
130
119
  .optional();
131
120
 
121
+ export const LandingFooterBrandSchema = z
122
+ .object({
123
+ name: z.string().optional(),
124
+ tagline: z.string().optional(),
125
+ logoSrc: z.string().optional(),
126
+ })
127
+ .optional();
128
+
129
+ export const LandingFooterColumnSchema = z.object({
130
+ title: z.string(),
131
+ links: z.array(LandingLinkSchema),
132
+ });
133
+
134
+ export const LandingFooterSocialSchema = z.object({
135
+ icon: z.string(),
136
+ href: z.string(),
137
+ label: z.string().optional(),
138
+ });
139
+
140
+ export const LandingFooterSchema = z
141
+ .object({
142
+ enabled: z.boolean().optional(),
143
+ brand: LandingFooterBrandSchema,
144
+ columns: z.array(LandingFooterColumnSchema).optional(),
145
+ copyright: z.string().optional(),
146
+ social: z.array(LandingFooterSocialSchema).optional(),
147
+ })
148
+ .optional();
149
+
132
150
  export const LandingContentSchema = z
133
151
  .object({
134
152
  hero: LandingHeroSchema,
@@ -136,10 +154,10 @@ export const LandingContentSchema = z
136
154
  features: z.array(LandingFeatureSchema).optional(),
137
155
  primaryAction: LandingLinkSchema.optional(),
138
156
  secondaryAction: LandingLinkSchema.optional(),
139
- ticker: LandingTickerSchema,
140
157
  trust: LandingTrustSchema,
141
158
  apiShowcase: LandingApiShowcaseSchema,
142
159
  pricingCta: LandingPricingCtaSchema,
160
+ footer: LandingFooterSchema,
143
161
  showPoweredBy: z.boolean().optional(),
144
162
  })
145
163
  .optional();
@@ -333,14 +351,20 @@ export type PlansContentConfig = z.infer<typeof PlansContentSchema>;
333
351
  export type PlanTierConfig = z.infer<typeof PlanTierSchema>;
334
352
 
335
353
  export type LandingCodeExampleConfig = z.infer<typeof LandingCodeExampleSchema>;
336
- export type LandingTickerItemConfig = z.infer<typeof LandingTickerItemSchema>;
337
- export type LandingTickerConfig = z.infer<typeof LandingTickerSchema>;
338
354
  export type LandingTrustConfig = z.infer<typeof LandingTrustSchema>;
339
355
  export type LandingApiShowcaseItemConfig = z.infer<
340
356
  typeof LandingApiShowcaseItemSchema
341
357
  >;
342
358
  export type LandingApiShowcaseConfig = z.infer<typeof LandingApiShowcaseSchema>;
343
359
  export type LandingPricingCtaConfig = z.infer<typeof LandingPricingCtaSchema>;
360
+ export type LandingFooterBrandConfig = z.infer<typeof LandingFooterBrandSchema>;
361
+ export type LandingFooterColumnConfig = z.infer<
362
+ typeof LandingFooterColumnSchema
363
+ >;
364
+ export type LandingFooterSocialConfig = z.infer<
365
+ typeof LandingFooterSocialSchema
366
+ >;
367
+ export type LandingFooterConfig = z.infer<typeof LandingFooterSchema>;
344
368
 
345
369
  export type ResolvedLandingContent = {
346
370
  hero?: {
@@ -360,10 +384,10 @@ export type ResolvedLandingContent = {
360
384
  features?: LandingFeatureConfig[];
361
385
  primaryAction?: LandingLinkConfig;
362
386
  secondaryAction?: LandingLinkConfig;
363
- ticker?: LandingTickerConfig;
364
387
  trust?: LandingTrustConfig;
365
388
  apiShowcase?: LandingApiShowcaseConfig;
366
389
  pricingCta?: LandingPricingCtaConfig;
390
+ footer?: LandingFooterConfig;
367
391
  showPoweredBy?: boolean;
368
392
  };
369
393
 
@@ -3,6 +3,7 @@ import type { CSSProperties, ReactNode } from "react";
3
3
  import type { FooterSocialIcons } from "../../config/validators/ZudokuConfig.js";
4
4
  import { cn } from "../util/cn.js";
5
5
  import { AnchorLink } from "./AnchorLink.js";
6
+ import { useRenderSlot } from "./context/SlotProvider.js";
6
7
  import { useZudoku } from "./index.js";
7
8
  import { Slot } from "./Slot.js";
8
9
 
@@ -30,6 +31,11 @@ export const Footer = () => {
30
31
  const { options } = useZudoku();
31
32
  const { site } = options;
32
33
  const footer = site?.footer;
34
+ // The "footer" slot fully replaces the default site footer on every page
35
+ // (used by the landing module to render its footer portal-wide).
36
+ const customFooter = useRenderSlot("footer");
37
+
38
+ if (customFooter.length > 0) return <>{customFooter}</>;
33
39
 
34
40
  if (!footer) return null;
35
41
 
@@ -13,6 +13,7 @@ export type CustomSlotNames = never;
13
13
  type PredefinedSlotNames =
14
14
  | "api-keys-list-page"
15
15
  | "api-keys-list-page-before-keys"
16
+ | "footer"
16
17
  | "footer-after"
17
18
  | "footer-before"
18
19
  | "head-navigation-end"
package/src/types.d.ts CHANGED
@@ -33,6 +33,10 @@ declare module "virtual:zudoku-api-keys-plugin" {
33
33
 
34
34
  declare module "virtual:zudoku-modules-plugin" {
35
35
  export const configuredModulesPlugins: import("./lib/core/plugins.ts").ApitogoPlugin[];
36
+ export const configuredModulesSlots: Record<
37
+ string,
38
+ import("./lib/components/context/SlotProvider.tsx").SlotType
39
+ >;
36
40
  }
37
41
 
38
42
  declare module "virtual:zudoku-custom-pages-plugin" {
@@ -35,7 +35,10 @@ const viteModulesPlugin = (): Plugin => {
35
35
  const { landing, userPanel } = config.__resolvedModules;
36
36
 
37
37
  if (config.__meta.mode === "standalone") {
38
- return `export const configuredModulesPlugins = [];`;
38
+ return [
39
+ `export const configuredModulesPlugins = [];`,
40
+ `export const configuredModulesSlots = {};`,
41
+ ].join("\n");
39
42
  }
40
43
 
41
44
  const landingImport = getLandingModuleImport(
@@ -107,6 +110,7 @@ const viteModulesPlugin = (): Plugin => {
107
110
  }
108
111
 
109
112
  const configuredPlugins: string[] = [];
113
+ let hasLandingFooterSlot = false;
110
114
 
111
115
  if (landing.enabled) {
112
116
  code.push(`import { landingModule } from "${landingImport}";`);
@@ -117,8 +121,24 @@ const viteModulesPlugin = (): Plugin => {
117
121
  `});`,
118
122
  );
119
123
  configuredPlugins.push("...(landing ? [landing] : [])");
124
+
125
+ // Render the configured landing footer on every page via the
126
+ // layout "footer" slot (home, docs, API reference, pricing, auth).
127
+ if (landing.content.footer) {
128
+ code.push(
129
+ `import { createLandingFooterSlot } from "${landingImport}";`,
130
+ `const landingFooterSlot = createLandingFooterSlot(${JSON.stringify(landing.content.footer)});`,
131
+ );
132
+ hasLandingFooterSlot = true;
133
+ }
120
134
  }
121
135
 
136
+ code.push(
137
+ hasLandingFooterSlot
138
+ ? `export const configuredModulesSlots = landingFooterSlot ? { footer: landingFooterSlot } : {};`
139
+ : `export const configuredModulesSlots = {};`,
140
+ );
141
+
122
142
  if (userPanel.enabled) {
123
143
  code.push(`import { userPanelModule } from "${userPanelImport}";`);
124
144
  code.push(