@lukoweb/apitogo 0.1.90 → 0.1.92

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
@@ -4878,6 +4878,13 @@ import {
4878
4878
  runnerImport,
4879
4879
  loadEnv as viteLoadEnv
4880
4880
  } from "vite";
4881
+ function resolvePublishGatewayBaseUrl() {
4882
+ const raw = process.env.APITOGO_GATEWAY_URL?.trim();
4883
+ if (!raw) {
4884
+ return void 0;
4885
+ }
4886
+ return raw.replace(/\/+$/, "");
4887
+ }
4881
4888
  function mergeJsonConfigWithRuntimeOverrides(fileConfig, importedConfig) {
4882
4889
  const jsonConfig = buildApitogoConfig({
4883
4890
  ...fileConfig,
@@ -5037,7 +5044,8 @@ async function loadZudokuConfig(configEnv, rootDir) {
5037
5044
  ...loadedConfig.__meta,
5038
5045
  pricingEnabled: isBillingEnabled(jsonConfig),
5039
5046
  authenticationEnabled: isAuthenticationEnabled(manifest),
5040
- previewPlans
5047
+ previewPlans,
5048
+ gatewayBaseUrl: resolvePublishGatewayBaseUrl()
5041
5049
  }
5042
5050
  };
5043
5051
  const transformedConfig = applyAuthenticationHeaderNav(
@@ -8132,11 +8140,18 @@ function buildPostPublishChecklist(args) {
8132
8140
  },
8133
8141
  {
8134
8142
  id: "test_pricing",
8135
- label: "Verify /pricing loads live plans",
8143
+ label: "Verify /pricing loads the public catalog (does not redirect to /account/plans)",
8136
8144
  required: true,
8137
8145
  completed: false,
8138
8146
  url: args.swaUrl ? `${args.swaUrl}/pricing` : void 0
8139
8147
  },
8148
+ {
8149
+ id: "test_account_plans",
8150
+ label: "Verify /account/plans requires sign-in and manages subscriptions",
8151
+ required: true,
8152
+ completed: false,
8153
+ url: args.swaUrl ? `${args.swaUrl}/account/plans` : void 0
8154
+ },
8140
8155
  {
8141
8156
  id: "test_credentials",
8142
8157
  label: "Verify /credentials returns a subscription key after plan selection",
@@ -12530,7 +12545,7 @@ import {
12530
12545
  // package.json
12531
12546
  var package_default = {
12532
12547
  name: "@lukoweb/apitogo",
12533
- version: "0.1.90",
12548
+ version: "0.1.92",
12534
12549
  type: "module",
12535
12550
  sideEffects: [
12536
12551
  "**/*.css",
@@ -15282,7 +15297,8 @@ var viteConfigPlugin = () => {
15282
15297
  rootDir: loadedConfig.__meta.rootDir,
15283
15298
  pricingEnabled: loadedConfig.__meta.pricingEnabled,
15284
15299
  authenticationEnabled: loadedConfig.__meta.authenticationEnabled,
15285
- previewPlans: loadedConfig.__meta.previewPlans
15300
+ previewPlans: loadedConfig.__meta.previewPlans,
15301
+ gatewayBaseUrl: loadedConfig.__meta.gatewayBaseUrl
15286
15302
  };
15287
15303
  return `
15288
15304
  import rawConfig from "${normalizePath(configPath)}";
@@ -16245,6 +16261,9 @@ import path39 from "node:path";
16245
16261
  import { normalizePath as normalizePath2 } from "vite";
16246
16262
  var getLandingModuleImport = (moduleDir, mode) => mode === "internal" ? normalizePath2(path39.join(moduleDir, "../module-landing/src/index.tsx")) : "@lukoweb/apitogo-module-landing";
16247
16263
  var getUserPanelModuleImport = (moduleDir, mode) => mode === "internal" ? normalizePath2(path39.join(moduleDir, "../module-user-panel/src/index.tsx")) : "@lukoweb/apitogo-module-user-panel";
16264
+ var getBillingPluginImport = (moduleDir, mode) => mode === "internal" ? normalizePath2(
16265
+ path39.join(moduleDir, "../plugin-dev-portal-billing/src/index.ts")
16266
+ ) : "@lukoweb/apitogo-plugin-dev-portal-billing";
16248
16267
  var resolveProjectPageImport = (rootDir, pagePath) => normalizePath2(path39.resolve(rootDir, pagePath));
16249
16268
  var viteModulesPlugin = () => {
16250
16269
  const virtualModuleId4 = "virtual:zudoku-modules-plugin";
@@ -16310,10 +16329,15 @@ var viteModulesPlugin = () => {
16310
16329
  `import LandingPage from "${resolveProjectPageImport(config2.__meta.rootDir, landingPagePath)}";`
16311
16330
  );
16312
16331
  }
16332
+ const useBillingDashboard = !dashboardPagePath && userPanel.plans.enabled;
16313
16333
  if (dashboardPagePath) {
16314
16334
  code.push(
16315
16335
  `import UserPanelDashboardPage from "${resolveProjectPageImport(config2.__meta.rootDir, dashboardPagePath)}";`
16316
16336
  );
16337
+ } else if (useBillingDashboard) {
16338
+ code.push(
16339
+ `import { OverviewDashboardPage as UserPanelDashboardPage } from "${getBillingPluginImport(config2.__meta.moduleDir, config2.__meta.mode)}";`
16340
+ );
16317
16341
  }
16318
16342
  if (profilePagePath) {
16319
16343
  code.push(
@@ -16354,7 +16378,7 @@ var viteModulesPlugin = () => {
16354
16378
  ` ...${JSON.stringify(userPanelConfig)},`,
16355
16379
  ` dashboard: {`,
16356
16380
  ` ...${JSON.stringify(userPanelConfig.dashboard)},`,
16357
- ` component: config.modules?.userPanel?.dashboard?.component${dashboardPagePath ? " ?? UserPanelDashboardPage" : ""},`,
16381
+ ` component: config.modules?.userPanel?.dashboard?.component${dashboardPagePath || useBillingDashboard ? " ?? UserPanelDashboardPage" : ""},`,
16358
16382
  ` },`,
16359
16383
  ` userManagement: {`,
16360
16384
  ` ...${JSON.stringify(userPanelConfig.userManagement)},`,
@@ -18680,6 +18704,9 @@ function patchApitogoConfigOpenApiToFile(content, input3 = "./openapi.json") {
18680
18704
  var ACCOUNT_PLANS_PAGE_PATH = "user-panel/AccountPlansPage.tsx";
18681
18705
  var ACCOUNT_PLANS_PAGE = `export { AccountPlansPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
18682
18706
  `;
18707
+ var DASHBOARD_PAGE_PATH = "user-panel/DashboardPage.tsx";
18708
+ var DASHBOARD_PAGE = `export { OverviewDashboardPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
18709
+ `;
18683
18710
  function patchAccountPlansPageConfig(content) {
18684
18711
  if (/plans:\s*\{[^}]*page:/s.test(content)) {
18685
18712
  return content;
@@ -18692,6 +18719,18 @@ function patchAccountPlansPageConfig(content) {
18692
18719
  }`
18693
18720
  );
18694
18721
  }
18722
+ function patchDashboardPageConfig(content) {
18723
+ if (/dashboard:\s*\{[^}]*page:/s.test(content)) {
18724
+ return content;
18725
+ }
18726
+ return content.replace(
18727
+ /dashboard:\s*\{\s*enabled:\s*true\s*\}/,
18728
+ `dashboard: {
18729
+ enabled: true,
18730
+ page: "./user-panel/DashboardPage.tsx",
18731
+ }`
18732
+ );
18733
+ }
18695
18734
  async function ensureAccountPlansPage(outputDirectory) {
18696
18735
  const pagePath = path53.join(outputDirectory, ACCOUNT_PLANS_PAGE_PATH);
18697
18736
  try {
@@ -18703,6 +18742,17 @@ async function ensureAccountPlansPage(outputDirectory) {
18703
18742
  return ACCOUNT_PLANS_PAGE_PATH;
18704
18743
  }
18705
18744
  }
18745
+ async function ensureDashboardPage(outputDirectory) {
18746
+ const pagePath = path53.join(outputDirectory, DASHBOARD_PAGE_PATH);
18747
+ try {
18748
+ await readFile13(pagePath, "utf8");
18749
+ return void 0;
18750
+ } catch {
18751
+ await mkdir10(path53.dirname(pagePath), { recursive: true });
18752
+ await writeFile12(pagePath, DASHBOARD_PAGE);
18753
+ return DASHBOARD_PAGE_PATH;
18754
+ }
18755
+ }
18706
18756
  function patchApitogoConfig(content, options) {
18707
18757
  let patched = content;
18708
18758
  if (options.title) {
@@ -18766,12 +18816,14 @@ async function patchScaffoldedSite(outputDirectory, options) {
18766
18816
  const configContent = await readFile13(configPath, "utf8");
18767
18817
  await writeFile12(
18768
18818
  configPath,
18769
- patchAccountPlansPageConfig(
18770
- patchApitogoConfig(configContent, {
18771
- title,
18772
- openApiUrl: options.openApiUrl,
18773
- logoUrl: options.branding?.logoUrl
18774
- })
18819
+ patchDashboardPageConfig(
18820
+ patchAccountPlansPageConfig(
18821
+ patchApitogoConfig(configContent, {
18822
+ title,
18823
+ openApiUrl: options.openApiUrl,
18824
+ logoUrl: options.branding?.logoUrl
18825
+ })
18826
+ )
18775
18827
  )
18776
18828
  );
18777
18829
  filesWritten.push("apitogo.config.tsx");
@@ -18779,6 +18831,10 @@ async function patchScaffoldedSite(outputDirectory, options) {
18779
18831
  if (accountPlansPage) {
18780
18832
  filesWritten.push(accountPlansPage);
18781
18833
  }
18834
+ const dashboardPage = await ensureDashboardPage(outputDirectory);
18835
+ if (dashboardPage) {
18836
+ filesWritten.push(dashboardPage);
18837
+ }
18782
18838
  return filesWritten;
18783
18839
  }
18784
18840
  async function scaffoldDevPortalSite(options) {
@@ -19131,7 +19187,10 @@ function createDefaultApitogoJsonConfig(options) {
19131
19187
  },
19132
19188
  userPanel: {
19133
19189
  enabled: true,
19134
- dashboard: { enabled: true },
19190
+ dashboard: {
19191
+ enabled: true,
19192
+ page: "./user-panel/DashboardPage.tsx"
19193
+ },
19135
19194
  userManagement: { enabled: true },
19136
19195
  plans: {
19137
19196
  enabled: true,
@@ -19956,6 +20015,8 @@ async function ensureTsconfigJson(dir) {
19956
20015
  var LANDING_PAGE_SOURCE = 'import {\n ApiShowcaseSection,\n FeaturesSection,\n HeroSection,\n LandingShell,\n PlansPreviewSection,\n PricingCtaSection,\n StepsSection,\n} from "@lukoweb/apitogo-module-landing";\n\nexport default function LandingPage() {\n return (\n <LandingShell\n title="Test against a real API before you build one"\n description="Sample API gives you realistic endpoints, authentication, rate limits, and webhooks \u2014 a fully working backend for prototypes, demos, and QA."\n >\n <HeroSection\n hero={{\n badge: "SANDBOX API",\n title: "Test against a real API before you build one",\n description:\n "Sample API gives you realistic endpoints, authentication, rate limits, and webhooks \u2014 a fully working backend for prototypes, demos, and QA. No setup, no infrastructure.",\n }}\n primaryAction={{ label: "Start for free", href: "/signup" }}\n secondaryAction={{ label: "Read the docs", href: "/introduction" }}\n codeExamples={[\n {\n filename: "Create a key",\n language: "bash",\n code: `$ curl -X POST https://api.sampleapi.dev/v1/keys\n# sk_live_9f2c7a1e...`,\n },\n {\n filename: "Call an endpoint",\n language: "bash",\n code: `$ curl https://api.sampleapi.dev/v1/users/42 \\\\\n -H "Authorization: Bearer sk_live_9f2c..."\n# { "id": 42, "name": "Priya Shah", "plan": "pro" }`,\n },\n ]}\n />\n <FeaturesSection\n section={{\n label: "Everything a real API has",\n title: "Built for testing, not just browsing",\n description:\n "Realistic data, live auth, rate limits, and webhooks \u2014 so you can ship with confidence.",\n }}\n features={[\n {\n title: "Realistic mock data",\n description:\n "Users, orders, products, and posts with relationships and pagination, not lorem ipsum.",\n icon: "boxes",\n },\n {\n title: "Real auth flow",\n description:\n "Every request checks a live API key, so you can test error handling and expiry.",\n icon: "shield-check",\n },\n {\n title: "Rate limiting",\n description:\n "Per-plan request limits and 429 responses, so you can build and test backoff logic.",\n icon: "gauge",\n },\n {\n title: "Webhooks",\n description:\n "Trigger test events and inspect delivered payloads and retries.",\n icon: "webhook",\n },\n {\n title: "OpenAPI & docs",\n description:\n "A full OpenAPI spec and reference docs for every endpoint.",\n icon: "book-open",\n },\n {\n title: "Usage analytics",\n description:\n "Track requests, latency, and errors per key from your dashboard.",\n icon: "activity",\n },\n ]}\n />\n <StepsSection\n steps={[\n {\n step: "01",\n title: "Create an account",\n description:\n "Sign up and land straight in your dashboard \u2014 no approval queue.",\n },\n {\n step: "02",\n title: "Generate a key",\n description:\n "Every key is scoped and rate-limited to your plan from the moment it\'s created.",\n },\n {\n step: "03",\n title: "Call any endpoint",\n description:\n "Users, orders, products, posts, and webhooks \u2014 all documented, all live.",\n },\n ]}\n />\n <ApiShowcaseSection\n apiShowcase={{\n title: "Sample endpoints",\n subtitle:\n "A slice of what\'s available \u2014 see the full reference in the docs.",\n browseAllHref: "/api",\n browseAllLabel: "Browse all APIs",\n layout: "list",\n items: [\n {\n name: "List paginated users",\n path: "/v1/users",\n method: "GET",\n summary: "List paginated users",\n },\n {\n name: "Create a mock order",\n path: "/v1/orders",\n method: "POST",\n summary: "Create a mock order",\n },\n {\n name: "Fetch one product",\n path: "/v1/products/{id}",\n method: "GET",\n summary: "Fetch one product",\n },\n {\n name: "List posts with comments",\n path: "/v1/posts",\n method: "GET",\n summary: "List posts with comments",\n },\n {\n name: "Fire a test webhook event",\n path: "/v1/webhooks/test",\n method: "POST",\n summary: "Fire a test webhook event",\n },\n {\n name: "Delete a mock order",\n path: "/v1/orders/{id}",\n method: "DELETE",\n summary: "Delete a mock order",\n },\n ],\n }}\n />\n <PlansPreviewSection\n plans={[\n {\n name: "Free",\n price: "$0",\n unit: "per month",\n blurb: "1,000 requests/mo, community support.",\n },\n {\n name: "Developer",\n price: "$19",\n unit: "per month",\n blurb: "50,000 requests/mo, webhooks included.",\n featured: true,\n },\n {\n name: "Team",\n price: "$79",\n unit: "per month",\n blurb: "500,000 requests/mo, 5 team seats.",\n },\n {\n name: "Enterprise",\n price: "Custom",\n unit: "talk to us",\n blurb: "Unlimited requests, SSO, SLA.",\n },\n ]}\n />\n <PricingCtaSection\n variant="banner"\n pricingCta={{\n title: "Start testing in minutes",\n description:\n "Free tier includes 1,000 requests a month \u2014 no card required.",\n primaryAction: { label: "Start for free", href: "/signup" },\n }}\n />\n </LandingShell>\n );\n}\n';
19957
20016
  var ACCOUNT_PLANS_PAGE_SOURCE = `export { AccountPlansPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
19958
20017
  `;
20018
+ var DASHBOARD_PAGE_SOURCE = `export { OverviewDashboardPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
20019
+ `;
19959
20020
  var fileExists2 = async (filePath) => {
19960
20021
  try {
19961
20022
  await access7(filePath, constants.F_OK);
@@ -19975,6 +20036,11 @@ async function ensurePortalStubFiles(dir) {
19975
20036
  await mkdir11(path57.join(dir, "user-panel"), { recursive: true });
19976
20037
  await writeFile14(plansPath, ACCOUNT_PLANS_PAGE_SOURCE, "utf8");
19977
20038
  }
20039
+ const dashboardPath = path57.join(dir, "user-panel", "DashboardPage.tsx");
20040
+ if (!await fileExists2(dashboardPath)) {
20041
+ await mkdir11(path57.join(dir, "user-panel"), { recursive: true });
20042
+ await writeFile14(dashboardPath, DASHBOARD_PAGE_SOURCE, "utf8");
20043
+ }
19978
20044
  await ensurePublicBrandingFiles(dir);
19979
20045
  }
19980
20046
  async function ensurePublicBrandingFiles(dir) {
@@ -13,6 +13,7 @@ export type ConfigWithMeta = ZudokuConfig & {
13
13
  pricingEnabled?: boolean;
14
14
  authenticationEnabled?: boolean;
15
15
  previewPlans?: DevPortalPreviewPlan[];
16
+ gatewayBaseUrl?: string;
16
17
  };
17
18
  __resolvedModules: ResolvedModulesConfig;
18
19
  };
@@ -1,4 +1,4 @@
1
- import type { ReactNode } from "react";
1
+ import { type ReactNode } from "react";
2
2
  export declare const LandingLayout: ({ children }: {
3
3
  children?: ReactNode;
4
4
  }) => import("react/jsx-runtime").JSX.Element;
@@ -6,7 +6,11 @@ description:
6
6
  behavior.
7
7
  ---
8
8
 
9
- **Gateway plans** (also called API plans) are what your **end users** subscribe to on `/pricing`.
9
+ **Gateway plans** (also called API plans) are what your **end users** subscribe to.
10
+
11
+ - **`/pricing`** — public catalog. Visitors choose a plan before signup. Signed-in users still see this page; it must not redirect to `/account/plans`.
12
+ - **`/account/plans`** — authenticated billing. Sign-in required. Manage and change subscriptions.
13
+
10
14
  They are separate from **platform hosting** (Free / Starter / Professional on APItoGo), which you
11
15
  select with `apitogo apply --platform-plan`.
12
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lukoweb/apitogo",
3
- "version": "0.1.90",
3
+ "version": "0.1.92",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -45,6 +45,8 @@ export type ConfigWithMeta = ZudokuConfig & {
45
45
  pricingEnabled?: boolean;
46
46
  authenticationEnabled?: boolean;
47
47
  previewPlans?: DevPortalPreviewPlan[];
48
+ /** Live gateway URL baked in at publish/build (`APITOGO_GATEWAY_URL`). */
49
+ gatewayBaseUrl?: string;
48
50
  };
49
51
  __resolvedModules: ResolvedModulesConfig;
50
52
  };
@@ -69,6 +71,15 @@ const zudokuConfigFiles = [
69
71
 
70
72
  const serverConfigOutputFiles = ["apitogo.config.js", "zudoku.config.js"];
71
73
 
74
+ function resolvePublishGatewayBaseUrl(): string | undefined {
75
+ const raw = process.env.APITOGO_GATEWAY_URL?.trim();
76
+ if (!raw) {
77
+ return undefined;
78
+ }
79
+
80
+ return raw.replace(/\/+$/, "");
81
+ }
82
+
72
83
  export function mergeJsonConfigWithRuntimeOverrides(
73
84
  fileConfig: Record<string, unknown>,
74
85
  importedConfig: ZudokuConfig,
@@ -304,6 +315,7 @@ export async function loadZudokuConfig(
304
315
  pricingEnabled: isBillingEnabled(jsonConfig),
305
316
  authenticationEnabled: isAuthenticationEnabled(manifest),
306
317
  previewPlans,
318
+ gatewayBaseUrl: resolvePublishGatewayBaseUrl(),
307
319
  },
308
320
  };
309
321
  const transformedConfig = applyAuthenticationHeaderNav(
@@ -1,5 +1,4 @@
1
1
  import type { LucideIcon } from "lucide-react";
2
- import { Link } from "react-router";
3
2
  import {
4
3
  isHeaderNavGroup,
5
4
  type HeaderNavGroup,
@@ -18,6 +17,7 @@ import {
18
17
  NavigationMenuTrigger,
19
18
  } from "../ui/NavigationMenu.js";
20
19
  import { cn } from "../util/cn.js";
20
+ import { AnchorLink } from "./AnchorLink.js";
21
21
  import { useZudoku } from "./context/ZudokuContext.js";
22
22
  import { shouldShowItem } from "./navigation/utils.js";
23
23
 
@@ -28,7 +28,7 @@ const NavLinkItem = ({ item }: { item: HeaderNavLinkItem }) => {
28
28
  const Icon = item.icon as LucideIcon | undefined;
29
29
  return (
30
30
  <NavigationMenuLink asChild>
31
- <Link
31
+ <AnchorLink
32
32
  to={item.to}
33
33
  target={item.target}
34
34
  rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
@@ -45,7 +45,7 @@ const NavLinkItem = ({ item }: { item: HeaderNavLinkItem }) => {
45
45
  {item.description}
46
46
  </p>
47
47
  )}
48
- </Link>
48
+ </AnchorLink>
49
49
  </NavigationMenuLink>
50
50
  );
51
51
  };
@@ -117,7 +117,7 @@ const HeaderNavItemComponent = ({
117
117
  return (
118
118
  <NavigationMenuItem>
119
119
  <NavigationMenuLink asChild>
120
- <Link
120
+ <AnchorLink
121
121
  to={item.to}
122
122
  target={item.target}
123
123
  rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
@@ -125,7 +125,7 @@ const HeaderNavItemComponent = ({
125
125
  >
126
126
  {Icon && <Icon size={16} />}
127
127
  {item.label}
128
- </Link>
128
+ </AnchorLink>
129
129
  </NavigationMenuLink>
130
130
  </NavigationMenuItem>
131
131
  );
@@ -1,21 +1,27 @@
1
1
  import { TooltipProvider } from "@lukoweb/apitogo/ui/Tooltip.js";
2
- import type { ReactNode } from "react";
3
- import { Suspense } from "react";
2
+ import { type ReactNode, Suspense } from "react";
4
3
  import { Outlet } from "react-router";
4
+ import { useScrollToAnchor } from "../util/useScrollToAnchor.js";
5
+ import { useScrollToTop } from "../util/useScrollToTop.js";
5
6
  import { Footer } from "./Footer.js";
6
7
  import { Header } from "./Header.js";
7
8
  import { Slot } from "./Slot.js";
8
9
 
9
- export const LandingLayout = ({ children }: { children?: ReactNode }) => (
10
- <TooltipProvider>
11
- <Slot.Target name="layout-before-head" />
12
- <Suspense>
13
- <Header />
14
- </Suspense>
15
- <Slot.Target name="layout-after-head" />
16
- <main className="min-h-[calc(100dvh-var(--top-header-height))] w-full">
17
- {children ?? <Outlet />}
18
- </main>
19
- <Footer />
20
- </TooltipProvider>
21
- );
10
+ export const LandingLayout = ({ children }: { children?: ReactNode }) => {
11
+ useScrollToAnchor();
12
+ useScrollToTop();
13
+
14
+ return (
15
+ <TooltipProvider>
16
+ <Slot.Target name="layout-before-head" />
17
+ <Suspense>
18
+ <Header />
19
+ </Suspense>
20
+ <Slot.Target name="layout-after-head" />
21
+ <main className="min-h-[calc(100dvh-var(--top-header-height))] w-full">
22
+ {children ?? <Outlet />}
23
+ </main>
24
+ <Footer />
25
+ </TooltipProvider>
26
+ );
27
+ };
@@ -28,6 +28,7 @@ import {
28
28
  DrawerTitle,
29
29
  DrawerTrigger,
30
30
  } from "../ui/Drawer.js";
31
+ import { AnchorLink } from "./AnchorLink.js";
31
32
  import { useCurrentNavigation, useZudoku } from "./context/ZudokuContext.js";
32
33
  import { PoweredByZudoku } from "./navigation/PoweredByZudoku.js";
33
34
  import { getFirstMatchingPath, shouldShowItem } from "./navigation/utils.js";
@@ -44,7 +45,7 @@ const MobileHeaderNavLink = ({
44
45
  }) => {
45
46
  const Icon = item.icon as LucideIcon | undefined;
46
47
  return (
47
- <Link
48
+ <AnchorLink
48
49
  to={item.to}
49
50
  target={item.target}
50
51
  rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
@@ -53,7 +54,7 @@ const MobileHeaderNavLink = ({
53
54
  >
54
55
  {Icon && <Icon size={16} />}
55
56
  {item.label}
56
- </Link>
57
+ </AnchorLink>
57
58
  );
58
59
  };
59
60
 
@@ -68,7 +69,7 @@ const MobileHeaderNavItem = ({
68
69
  const Icon = item.icon as LucideIcon | undefined;
69
70
  return (
70
71
  <li className="w-full">
71
- <Link
72
+ <AnchorLink
72
73
  to={item.to}
73
74
  target={item.target}
74
75
  rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
@@ -77,7 +78,7 @@ const MobileHeaderNavItem = ({
77
78
  >
78
79
  {Icon && <Icon size={16} />}
79
80
  {item.label}
80
- </Link>
81
+ </AnchorLink>
81
82
  </li>
82
83
  );
83
84
  }
@@ -57,6 +57,11 @@ export const useScrollToAnchor = () => {
57
57
  return;
58
58
  }
59
59
 
60
+ if (scrollToHash(location.hash)) {
61
+ initialScrolled.current = true;
62
+ return;
63
+ }
64
+
60
65
  const observer = new MutationObserver((_, obs) => {
61
66
  if (!scrollToHash(location.hash)) return;
62
67
  initialScrolled.current = true;
@@ -69,6 +69,7 @@ const viteConfigPlugin = (): Plugin => {
69
69
  pricingEnabled: loadedConfig.__meta.pricingEnabled,
70
70
  authenticationEnabled: loadedConfig.__meta.authenticationEnabled,
71
71
  previewPlans: loadedConfig.__meta.previewPlans,
72
+ gatewayBaseUrl: loadedConfig.__meta.gatewayBaseUrl,
72
73
  };
73
74
 
74
75
  return `
@@ -14,6 +14,13 @@ const getUserPanelModuleImport = (moduleDir: string, mode: string) =>
14
14
  ? normalizePath(path.join(moduleDir, "../module-user-panel/src/index.tsx"))
15
15
  : "@lukoweb/apitogo-module-user-panel";
16
16
 
17
+ const getBillingPluginImport = (moduleDir: string, mode: string) =>
18
+ mode === "internal"
19
+ ? normalizePath(
20
+ path.join(moduleDir, "../plugin-dev-portal-billing/src/index.ts"),
21
+ )
22
+ : "@lukoweb/apitogo-plugin-dev-portal-billing";
23
+
17
24
  const resolveProjectPageImport = (rootDir: string, pagePath: string) =>
18
25
  normalizePath(path.resolve(rootDir, pagePath));
19
26
 
@@ -91,10 +98,17 @@ const viteModulesPlugin = (): Plugin => {
91
98
  );
92
99
  }
93
100
 
101
+ const useBillingDashboard =
102
+ !dashboardPagePath && userPanel.plans.enabled;
103
+
94
104
  if (dashboardPagePath) {
95
105
  code.push(
96
106
  `import UserPanelDashboardPage from "${resolveProjectPageImport(config.__meta.rootDir, dashboardPagePath)}";`,
97
107
  );
108
+ } else if (useBillingDashboard) {
109
+ code.push(
110
+ `import { OverviewDashboardPage as UserPanelDashboardPage } from "${getBillingPluginImport(config.__meta.moduleDir, config.__meta.mode)}";`,
111
+ );
98
112
  }
99
113
 
100
114
  if (profilePagePath) {
@@ -146,7 +160,7 @@ const viteModulesPlugin = (): Plugin => {
146
160
  ` ...${JSON.stringify(userPanelConfig)},`,
147
161
  ` dashboard: {`,
148
162
  ` ...${JSON.stringify(userPanelConfig.dashboard)},`,
149
- ` component: config.modules?.userPanel?.dashboard?.component${dashboardPagePath ? " ?? UserPanelDashboardPage" : ""},`,
163
+ ` component: config.modules?.userPanel?.dashboard?.component${dashboardPagePath || useBillingDashboard ? " ?? UserPanelDashboardPage" : ""},`,
150
164
  ` },`,
151
165
  ` userManagement: {`,
152
166
  ` ...${JSON.stringify(userPanelConfig.userManagement)},`,