@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 +78 -12
- package/dist/declarations/config/loader.d.ts +1 -0
- package/dist/declarations/lib/components/LandingLayout.d.ts +1 -1
- package/docs/configuration/plans.md +5 -1
- package/package.json +1 -1
- package/src/config/loader.ts +12 -0
- package/src/lib/components/HeaderNavigation.tsx +5 -5
- package/src/lib/components/LandingLayout.tsx +21 -15
- package/src/lib/components/MobileTopNavigation.tsx +5 -4
- package/src/lib/util/useScrollToAnchor.ts +5 -0
- package/src/vite/plugin-config.ts +1 -0
- package/src/vite/plugin-modules.ts +15 -1
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
|
|
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.
|
|
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
|
-
|
|
18770
|
-
|
|
18771
|
-
|
|
18772
|
-
|
|
18773
|
-
|
|
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: {
|
|
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) {
|
|
@@ -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
|
|
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
package/src/config/loader.ts
CHANGED
|
@@ -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
|
-
<
|
|
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
|
-
</
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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)},`,
|