@lukoweb/apitogo 0.1.58 → 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, 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({
@@ -750,6 +750,27 @@ var init_ModulesSchema = __esm({
750
750
  primaryAction: LandingLinkSchema.optional(),
751
751
  secondaryAction: LandingLinkSchema.optional()
752
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();
753
774
  LandingContentSchema = z3.object({
754
775
  hero: LandingHeroSchema,
755
776
  featuresSection: LandingFeaturesSectionSchema,
@@ -759,6 +780,7 @@ var init_ModulesSchema = __esm({
759
780
  trust: LandingTrustSchema,
760
781
  apiShowcase: LandingApiShowcaseSchema,
761
782
  pricingCta: LandingPricingCtaSchema,
783
+ footer: LandingFooterSchema,
762
784
  showPoweredBy: z3.boolean().optional()
763
785
  }).optional();
764
786
  DashboardContentSchema = z3.object({
@@ -1240,6 +1262,7 @@ var init_resolve_modules = __esm({
1240
1262
  const siteTitle = config2.metadata?.applicationName ?? config2.site?.title ?? "APIToGo";
1241
1263
  if (options?.useDefaults === true) {
1242
1264
  const defaults = buildDefaultLandingContent(config2);
1265
+ const footer2 = mergeLandingSection(defaults.footer, content?.footer);
1243
1266
  return {
1244
1267
  hero: {
1245
1268
  ...defaults.hero,
@@ -1256,13 +1279,15 @@ var init_resolve_modules = __esm({
1256
1279
  features: content?.features ?? defaults.features,
1257
1280
  primaryAction: content?.primaryAction ?? defaults.primaryAction,
1258
1281
  secondaryAction: content?.secondaryAction ?? defaults.secondaryAction,
1259
- showPoweredBy: content?.showPoweredBy ?? defaults.showPoweredBy,
1282
+ // A configured footer replaces the "Built with APIToGo" powered-by line.
1283
+ showPoweredBy: content?.showPoweredBy ?? (footer2 ? false : defaults.showPoweredBy),
1260
1284
  trust: mergeLandingSection(defaults.trust, content?.trust),
1261
1285
  apiShowcase: mergeLandingSection(
1262
1286
  defaults.apiShowcase,
1263
1287
  content?.apiShowcase
1264
1288
  ),
1265
- pricingCta: mergeLandingSection(defaults.pricingCta, content?.pricingCta)
1289
+ pricingCta: mergeLandingSection(defaults.pricingCta, content?.pricingCta),
1290
+ footer: footer2
1266
1291
  };
1267
1292
  }
1268
1293
  const hasContent = content !== void 0 && Object.keys(content).length > 0;
@@ -1272,6 +1297,7 @@ var init_resolve_modules = __esm({
1272
1297
  showPoweredBy: true
1273
1298
  };
1274
1299
  }
1300
+ const footer = content.footer ? mergeLandingSection(void 0, content.footer) : void 0;
1275
1301
  return {
1276
1302
  hero: {
1277
1303
  ...content.hero,
@@ -1281,10 +1307,12 @@ var init_resolve_modules = __esm({
1281
1307
  features: content.features,
1282
1308
  primaryAction: content.primaryAction,
1283
1309
  secondaryAction: content.secondaryAction,
1284
- showPoweredBy: content.showPoweredBy ?? true,
1310
+ // A configured footer replaces the "Built with APIToGo" powered-by line.
1311
+ showPoweredBy: content.showPoweredBy ?? !footer,
1285
1312
  trust: content.trust ? mergeLandingSection(void 0, content.trust) : void 0,
1286
1313
  apiShowcase: content.apiShowcase ? mergeLandingSection(void 0, content.apiShowcase) : void 0,
1287
- pricingCta: content.pricingCta ? mergeLandingSection(void 0, content.pricingCta) : void 0
1314
+ pricingCta: content.pricingCta ? mergeLandingSection(void 0, content.pricingCta) : void 0,
1315
+ footer
1288
1316
  };
1289
1317
  };
1290
1318
  DEFAULT_DASHBOARD_QUICK_LINKS = [
@@ -5199,7 +5227,7 @@ import {
5199
5227
  // package.json
5200
5228
  var package_default = {
5201
5229
  name: "@lukoweb/apitogo",
5202
- version: "0.1.58",
5230
+ version: "0.1.59",
5203
5231
  type: "module",
5204
5232
  sideEffects: [
5205
5233
  "**/*.css",
@@ -8898,7 +8926,10 @@ var viteModulesPlugin = () => {
8898
8926
  const config2 = getCurrentConfig();
8899
8927
  const { landing, userPanel } = config2.__resolvedModules;
8900
8928
  if (config2.__meta.mode === "standalone") {
8901
- return `export const configuredModulesPlugins = [];`;
8929
+ return [
8930
+ `export const configuredModulesPlugins = [];`,
8931
+ `export const configuredModulesSlots = {};`
8932
+ ].join("\n");
8902
8933
  }
8903
8934
  const landingImport = getLandingModuleImport(
8904
8935
  config2.__meta.moduleDir,
@@ -8960,6 +8991,7 @@ var viteModulesPlugin = () => {
8960
8991
  );
8961
8992
  }
8962
8993
  const configuredPlugins = [];
8994
+ let hasLandingFooterSlot = false;
8963
8995
  if (landing.enabled) {
8964
8996
  code.push(`import { landingModule } from "${landingImport}";`);
8965
8997
  code.push(
@@ -8969,7 +9001,17 @@ var viteModulesPlugin = () => {
8969
9001
  `});`
8970
9002
  );
8971
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
+ }
8972
9011
  }
9012
+ code.push(
9013
+ hasLandingFooterSlot ? `export const configuredModulesSlots = landingFooterSlot ? { footer: landingFooterSlot } : {};` : `export const configuredModulesSlots = {};`
9014
+ );
8973
9015
  if (userPanel.enabled) {
8974
9016
  code.push(`import { userPanelModule } from "${userPanelImport}";`);
8975
9017
  code.push(
@@ -65,6 +65,27 @@ export declare const LandingManifestContentSchema: z.ZodOptional<z.ZodObject<{
65
65
  href: z.ZodString;
66
66
  }, z.core.$strip>>;
67
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>>;
68
89
  showPoweredBy: z.ZodOptional<z.ZodBoolean>;
69
90
  }, z.core.$strip>>;
70
91
  export type LandingManifestContent = z.infer<typeof LandingContentSchema>;
@@ -153,6 +153,27 @@ export declare const DevPortalLocalManifestSchema: z.ZodObject<{
153
153
  href: z.ZodString;
154
154
  }, z.core.$strip>>;
155
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>>;
156
177
  showPoweredBy: z.ZodOptional<z.ZodBoolean>;
157
178
  }, z.core.$strip>>;
158
179
  }, z.core.$strip>>;
@@ -92,6 +92,44 @@ export declare const LandingPricingCtaSchema: z.ZodOptional<z.ZodObject<{
92
92
  href: z.ZodString;
93
93
  }, z.core.$strip>>;
94
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>>;
95
133
  export declare const LandingContentSchema: z.ZodOptional<z.ZodObject<{
96
134
  hero: z.ZodOptional<z.ZodObject<{
97
135
  title: z.ZodOptional<z.ZodString>;
@@ -157,6 +195,27 @@ export declare const LandingContentSchema: z.ZodOptional<z.ZodObject<{
157
195
  href: z.ZodString;
158
196
  }, z.core.$strip>>;
159
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>>;
160
219
  showPoweredBy: z.ZodOptional<z.ZodBoolean>;
161
220
  }, z.core.$strip>>;
162
221
  export declare const DashboardContentSchema: z.ZodOptional<z.ZodObject<{
@@ -265,6 +324,27 @@ export declare const LandingModuleSchema: z.ZodObject<{
265
324
  href: z.ZodString;
266
325
  }, z.core.$strip>>;
267
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>>;
268
348
  showPoweredBy: z.ZodOptional<z.ZodBoolean>;
269
349
  }, z.core.$strip>>;
270
350
  useDefaults: z.ZodOptional<z.ZodBoolean>;
@@ -494,6 +574,27 @@ export declare const ModulesSchema: z.ZodOptional<z.ZodObject<{
494
574
  href: z.ZodString;
495
575
  }, z.core.$strip>>;
496
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>>;
497
598
  showPoweredBy: z.ZodOptional<z.ZodBoolean>;
498
599
  }, z.core.$strip>>;
499
600
  useDefaults: z.ZodOptional<z.ZodBoolean>;
@@ -588,6 +689,10 @@ export type LandingTrustConfig = z.infer<typeof LandingTrustSchema>;
588
689
  export type LandingApiShowcaseItemConfig = z.infer<typeof LandingApiShowcaseItemSchema>;
589
690
  export type LandingApiShowcaseConfig = z.infer<typeof LandingApiShowcaseSchema>;
590
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>;
591
696
  export type ResolvedLandingContent = {
592
697
  hero?: {
593
698
  title?: string;
@@ -609,6 +714,7 @@ export type ResolvedLandingContent = {
609
714
  trust?: LandingTrustConfig;
610
715
  apiShowcase?: LandingApiShowcaseConfig;
611
716
  pricingCta?: LandingPricingCtaConfig;
717
+ footer?: LandingFooterConfig;
612
718
  showPoweredBy?: boolean;
613
719
  };
614
720
  export type ResolvedLandingModule = {
@@ -6952,6 +6952,27 @@ export declare const ZudokuConfig: z.ZodObject<{
6952
6952
  href: z.ZodString;
6953
6953
  }, z.core.$strip>>;
6954
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>>;
6955
6976
  showPoweredBy: z.ZodOptional<z.ZodBoolean>;
6956
6977
  }, z.core.$strip>>;
6957
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, }: {
@@ -589,6 +589,24 @@ export interface _Schema21 {
589
589
  primaryAction?: _Schema23
590
590
  secondaryAction?: _Schema23
591
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
+ }
592
610
  showPoweredBy?: boolean
593
611
  }
594
612
  export interface _Schema23 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lukoweb/apitogo",
3
- "version": "0.1.58",
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,
@@ -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,13 +119,16 @@ 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
+ // A configured footer replaces the "Built with APIToGo" powered-by line.
123
+ showPoweredBy:
124
+ content?.showPoweredBy ?? (footer ? false : defaults.showPoweredBy),
122
125
  trust: mergeLandingSection(defaults.trust, content?.trust),
123
126
  apiShowcase: mergeLandingSection(
124
127
  defaults.apiShowcase,
125
128
  content?.apiShowcase,
126
129
  ),
127
130
  pricingCta: mergeLandingSection(defaults.pricingCta, content?.pricingCta),
131
+ footer,
128
132
  };
129
133
  }
130
134
 
@@ -137,6 +141,10 @@ export const resolveLandingContent = (
137
141
  };
138
142
  }
139
143
 
144
+ const footer = content.footer
145
+ ? mergeLandingSection(undefined, content.footer)
146
+ : undefined;
147
+
140
148
  return {
141
149
  hero: {
142
150
  ...content.hero,
@@ -148,7 +156,8 @@ export const resolveLandingContent = (
148
156
  features: content.features,
149
157
  primaryAction: content.primaryAction,
150
158
  secondaryAction: content.secondaryAction,
151
- showPoweredBy: content.showPoweredBy ?? true,
159
+ // A configured footer replaces the "Built with APIToGo" powered-by line.
160
+ showPoweredBy: content.showPoweredBy ?? !footer,
152
161
  trust: content.trust
153
162
  ? mergeLandingSection(undefined, content.trust)
154
163
  : undefined,
@@ -158,6 +167,7 @@ export const resolveLandingContent = (
158
167
  pricingCta: content.pricingCta
159
168
  ? mergeLandingSection(undefined, content.pricingCta)
160
169
  : undefined,
170
+ footer,
161
171
  };
162
172
  };
163
173
 
@@ -118,6 +118,35 @@ export const LandingPricingCtaSchema = z
118
118
  })
119
119
  .optional();
120
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
+
121
150
  export const LandingContentSchema = z
122
151
  .object({
123
152
  hero: LandingHeroSchema,
@@ -128,6 +157,7 @@ export const LandingContentSchema = z
128
157
  trust: LandingTrustSchema,
129
158
  apiShowcase: LandingApiShowcaseSchema,
130
159
  pricingCta: LandingPricingCtaSchema,
160
+ footer: LandingFooterSchema,
131
161
  showPoweredBy: z.boolean().optional(),
132
162
  })
133
163
  .optional();
@@ -327,6 +357,14 @@ export type LandingApiShowcaseItemConfig = z.infer<
327
357
  >;
328
358
  export type LandingApiShowcaseConfig = z.infer<typeof LandingApiShowcaseSchema>;
329
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>;
330
368
 
331
369
  export type ResolvedLandingContent = {
332
370
  hero?: {
@@ -349,6 +387,7 @@ export type ResolvedLandingContent = {
349
387
  trust?: LandingTrustConfig;
350
388
  apiShowcase?: LandingApiShowcaseConfig;
351
389
  pricingCta?: LandingPricingCtaConfig;
390
+ footer?: LandingFooterConfig;
352
391
  showPoweredBy?: boolean;
353
392
  };
354
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(