From bcd69d70c1d8388db8e83e0dc8ab1441d0f57355 Mon Sep 17 00:00:00 2001 From: icyleaf Date: Thu, 8 Jan 2026 00:24:48 +0800 Subject: [PATCH 01/26] feat: update pricing page --- .gitignore | 1 + docusaurus.config.js | 211 +++-- package.json | 11 +- pnpm-lock.yaml | 1266 +++++++++++++++++-------- postcss.config.js | 5 + src/components/Pricing/PriceAmount.js | 33 + src/components/Pricing/PriceCards.js | 35 + src/components/Pricing/PriceTitle.js | 33 + src/components/Pricing/index.js | 36 + src/components/ui/Skeleton.js | 12 + src/constants/pricing-tier.js | 90 ++ src/constants/regions.js | 5 + src/css/custom.css | 11 +- src/hooks/usePaddlePrices.js | 34 + src/lib/utils.js | 6 + src/pages/pricing.js | 185 +++- src/plugins/tailwind-config.js | 9 + 17 files changed, 1437 insertions(+), 546 deletions(-) create mode 100644 postcss.config.js create mode 100644 src/components/Pricing/PriceAmount.js create mode 100644 src/components/Pricing/PriceCards.js create mode 100644 src/components/Pricing/PriceTitle.js create mode 100644 src/components/Pricing/index.js create mode 100644 src/components/ui/Skeleton.js create mode 100644 src/constants/pricing-tier.js create mode 100644 src/constants/regions.js create mode 100644 src/hooks/usePaddlePrices.js create mode 100644 src/lib/utils.js create mode 100644 src/plugins/tailwind-config.js diff --git a/.gitignore b/.gitignore index b2d6de306..b083ed4f9 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ # Misc .DS_Store +.env .env.local .env.development.local .env.test.local diff --git a/docusaurus.config.js b/docusaurus.config.js index ecd480e64..2f00af519 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -1,22 +1,24 @@ // @ts-check // Note: type annotations allow type checking and IDEs autocompletion -const isDev = process.env.NODE_ENV === 'development'; +const isDev = process.env.NODE_ENV === "development"; -const isDeployPreview = !!process.env.NETLIFY && process.env.CONTEXT === 'deploy-preview'; +const isDeployPreview = + !!process.env.NETLIFY && process.env.CONTEXT === "deploy-preview"; // Netlify branch deploy like "docusaurus-v2" -const isBranchDeploy = !!process.env.NETLIFY && process.env.CONTEXT === 'branch-deploy'; +const isBranchDeploy = + !!process.env.NETLIFY && process.env.CONTEXT === "branch-deploy"; /** @type {Array} */ -const versions = require('./versions.json'); +const versions = require("./versions.json"); /** @param {string} version */ function isPrerelease(version) { return ( - version.includes('alpha') || - version.includes('beta') || - version.includes('rc') + version.includes("alpha") || + version.includes("beta") || + version.includes("rc") ); } @@ -27,41 +29,45 @@ function getLastVersion() { // This probably only makes sense for the alpha/beta/rc phase, temporary function getNextVersionName() { - return 'Next'; + return "Next"; } /** @type {import('@docusaurus/types').Config} */ const config = { - title: 'Zealot', - tagline: 'Self-hosted Beta App Distribution for Android, iOS, macOS, Windows and Linux apps.', - url: 'https://zealot.ews.im', - baseUrl: '/', - onBrokenLinks: 'throw', - favicon: 'img/zealot.png', + title: "Zealot", + tagline: + "Self-hosted Beta App Distribution for Android, iOS, macOS, Windows and Linux apps.", + url: "https://zealot.ews.im", + baseUrl: "/", + onBrokenLinks: "throw", + favicon: "img/zealot.png", i18n: { - defaultLocale: 'en', - locales: ['en', 'zh-Hans'], + defaultLocale: "en", + locales: ["en", "zh-Hans"], }, markdown: { hooks: { - onBrokenMarkdownLinks: 'warn', - } + onBrokenMarkdownLinks: "warn", + }, }, - organizationName: 'tryzealot', - projectName: 'docs', + organizationName: "tryzealot", + projectName: "docs", presets: [ [ - 'classic', + "classic", /** @type {import('@docusaurus/preset-classic').Options} */ ({ docs: { - sidebarPath: require.resolve('./sidebars.js'), + sidebarPath: require.resolve("./sidebars.js"), // Please change this to your repo. // Remove this to remove the "edit this page" links. - editUrl: 'https://github.com/tryzealot/docs/tree/main/', - lastVersion: isDev || isDeployPreview || isBranchDeploy ? 'current' : getLastVersion(), + editUrl: "https://github.com/tryzealot/docs/tree/main/", + lastVersion: + isDev || isDeployPreview || isBranchDeploy + ? "current" + : getLastVersion(), versions: { current: { label: `${getNextVersionName()} 🚧`, @@ -69,33 +75,33 @@ const config = { }, }, theme: { - customCss: require.resolve('./src/css/custom.css'), + customCss: require.resolve("./src/css/custom.css"), }, sitemap: { - changefreq: 'weekly', + changefreq: "weekly", priority: 0.5, - ignorePatterns: ['/tags/**'], - filename: 'sitemap.xml', + ignorePatterns: ["/tags/**"], + filename: "sitemap.xml", }, }), ], [ - 'redocusaurus', + "redocusaurus", /** @type {import('redocusaurus').PresetOptions} */ { specs: [ { - spec: 'openapi_v1_en.json', - route: '/api/v1/en', + spec: "openapi_v1_en.json", + route: "/api/v1/en", }, { - spec: 'openapi_v1_zh-Hans.json', - route: 'api/v1/zh-Hans', + spec: "openapi_v1_zh-Hans.json", + route: "api/v1/zh-Hans", }, ], theme: { - primaryColor: '#1890ff', + primaryColor: "#1890ff", }, }, ], @@ -105,112 +111,112 @@ const config = { /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ navbar: { - title: 'Zealot', + title: "Zealot", logo: { - alt: 'Zealot Logo', - src: 'img/zealot.png', - srcDark: 'img/zealot-dark.png', + alt: "Zealot Logo", + src: "img/zealot.png", + srcDark: "img/zealot-dark.png", }, items: [ { - type: 'doc', - docId: 'self-hosted/index', - label: 'Self Hosted', - position: 'left', + type: "doc", + docId: "self-hosted/index", + label: "Self Hosted", + position: "left", }, { - type: 'doc', - docId: 'developer-guide/index', - position: 'left', - label: 'Developer Guide', + type: "doc", + docId: "developer-guide/index", + position: "left", + label: "Developer Guide", }, { - type: 'doc', - docId: 'user-guide/index', - position: 'left', - label: 'User Guide', + type: "doc", + docId: "user-guide/index", + position: "left", + label: "User Guide", }, { - type: 'doc', - docId: 'user-guide/changelog', - position: 'left', - label: 'Changelog', + type: "doc", + docId: "user-guide/changelog", + position: "left", + label: "Changelog", }, { type: "doc", docId: "developer-guide/api", - position: 'left', + position: "left", label: "API", }, { href: "/donate", - position: 'right', + position: "right", label: "Donate", }, { - type: 'docsVersionDropdown', - position: 'right', + type: "docsVersionDropdown", + position: "right", dropdownActiveClassDisabled: true, dropdownItemsAfter: [ { - type: 'html', + type: "html", value: '', }, { - href: 'https://tryzealot.github.io/docs-legacy', - label: 'Legacy docs', - } + href: "https://tryzealot.github.io/docs-legacy", + label: "Legacy docs", + }, ], }, { - type: 'localeDropdown', - position: 'right', - } + type: "localeDropdown", + position: "right", + }, ], }, footer: { - style: 'dark', + style: "dark", links: [ { - title: 'Community', + title: "Community", items: [ { - label: 'Telegram', - href: 'https://t.me/+csa3Y2KOx44wMGRl', + label: "Telegram", + href: "https://t.me/+csa3Y2KOx44wMGRl", }, { - label: 'Github Discussions', - href: 'https://github.com/tryzealot/zealot/discussions', + label: "Github Discussions", + href: "https://github.com/tryzealot/zealot/discussions", }, ], }, { - title: 'More', + title: "More", items: [ { - label: 'Demo', - href: 'https://tryzealot.ews.im', + label: "Demo", + href: "https://tryzealot.ews.im", }, { - label: 'GitHub', - href: 'https://github.com/tryzealot/zealot', + label: "GitHub", + href: "https://github.com/tryzealot/zealot", }, ], }, { - title: 'Legal', + title: "Legal", items: [ { - label: 'Terms of Service', - href: '/terms', + label: "Terms of Service", + href: "/terms", }, { - label: 'Privacy Policy', - href: '/privacy', + label: "Privacy Policy", + href: "/privacy", }, { - label: 'EULA', - href: '/eula', + label: "EULA", + href: "/eula", }, ], }, @@ -218,23 +224,31 @@ const config = { copyright: `Copyright © 2018 - ${new Date().getFullYear()} Zealot.`, }, prism: { - additionalLanguages: ['kotlin', 'swift', 'groovy', 'ruby', 'nginx', 'toml', 'hcl'], + additionalLanguages: [ + "kotlin", + "swift", + "groovy", + "ruby", + "nginx", + "toml", + "hcl", + ], magicComments: [ { - className: 'theme-code-block-highlighted-line', - line: 'highlight-next-line', + className: "theme-code-block-highlighted-line", + line: "highlight-next-line", block: { - start: 'highlight-start', - end: 'highlight-end' + start: "highlight-start", + end: "highlight-end", }, }, { - className: 'code-block-error-line', - line: 'This will error', + className: "code-block-error-line", + line: "This will error", }, ], - theme: require('prism-react-renderer').themes.github, - darkTheme: require('prism-react-renderer').themes.duotoneDark, + theme: require("prism-react-renderer").themes.github, + darkTheme: require("prism-react-renderer").themes.duotoneDark, }, // announcementBar: { // id: 'announcementBar-2', @@ -246,12 +260,19 @@ const config = { }), plugins: [ + ["./src/plugins/tailwind-config.js", {}], + [ + "docusaurus2-dotenv", + { + systemvars: true, + }, + ], [ "@gracefullight/docusaurus-plugin-microsoft-clarity", { - projectId: "mgce1iwvr4" - } - ] + projectId: "mgce1iwvr4", + }, + ], ], future: { v4: { diff --git a/package.json b/package.json index 6048c0982..42b67d2c3 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,10 @@ "@docusaurus/tsconfig": "^3.9.2", "@gracefullight/docusaurus-plugin-microsoft-clarity": "^1.0.0", "@mdx-js/react": "^3.1.1", + "@paddle/paddle-js": "^1.6.1", + "@paddle/paddle-node-sdk": "^3.5.0", "clsx": "^2.1.1", + "docusaurus2-dotenv": "^1.4.0", "prism-react-renderer": "^2.4.1", "react": "19.0.0", "react-dom": "19.0.0", @@ -34,7 +37,13 @@ "@babel/helper-explode-assignable-expression": "^7.18.6", "@crowdin/cli": "^4.13.0", "@docusaurus/module-type-aliases": "^3.9.2", - "@docusaurus/types": "^3.9.2" + "@docusaurus/types": "^3.9.2", + "@tailwindcss/postcss": "^4.1.18", + "postcss": "^8.5.6", + "prettier-plugin-tailwindcss": "^0.7.2", + "tailwind-merge": "^3.4.0", + "tailwindcss": "^4.1.18", + "tailwindcss-animate": "^1.0.7" }, "browserslist": { "production": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 692132468..483a36b76 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,9 +23,18 @@ importers: '@mdx-js/react': specifier: ^3.1.1 version: 3.1.1(@types/react@19.2.9)(react@19.0.0) + '@paddle/paddle-js': + specifier: ^1.6.1 + version: 1.6.1 + '@paddle/paddle-node-sdk': + specifier: ^3.5.0 + version: 3.5.0 clsx: specifier: ^2.1.1 version: 2.1.1 + docusaurus2-dotenv: + specifier: ^1.4.0 + version: 1.4.0(webpack@5.99.8) prism-react-renderer: specifier: ^2.4.1 version: 2.4.1(react@19.0.0) @@ -51,6 +60,24 @@ importers: '@docusaurus/types': specifier: ^3.9.2 version: 3.9.2(acorn@8.14.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@tailwindcss/postcss': + specifier: ^4.1.18 + version: 4.1.18 + postcss: + specifier: ^8.5.6 + version: 8.5.6 + prettier-plugin-tailwindcss: + specifier: ^0.7.2 + version: 0.7.2(prettier@3.7.4) + tailwind-merge: + specifier: ^3.4.0 + version: 3.4.0 + tailwindcss: + specifier: ^4.1.18 + version: 4.1.18 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@4.1.18) packages: @@ -133,6 +160,10 @@ packages: resolution: {integrity: sha512-5qCRoySnzpbQVg2IPLGFCm4LF75pToxI5tdjOYgUMNL/um91aJ4dH3SVdBEuFlVsalxl8mh3bWPgkUmv6NpJiQ==} engines: {node: '>= 14.0.0'} + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -685,10 +716,6 @@ packages: resolution: {integrity: sha512-909rVuj3phpjW6y0MCXAZ5iNeORePa6ldJvp2baWGcTjwqbBDDz6xoS5JHJ7lS88NlwLYj07ImL/8IUMtDZzTA==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.28.3': - resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.28.6': resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} @@ -1224,6 +1251,9 @@ packages: resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -1238,6 +1268,9 @@ packages: '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} @@ -1301,6 +1334,13 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@paddle/paddle-js@1.6.1': + resolution: {integrity: sha512-Yh6i99Wz22EcJ0KyZfTxzzDHduet3fpKmqEKSmZlHxlgIXDyBjkzdGNWw3/yR7Esdvr6HuLu3WfDTod5lC0alw==} + + '@paddle/paddle-node-sdk@3.5.0': + resolution: {integrity: sha512-/Ac1N5i01/YJhiqOKlfW+Rz+nfhkaCyqpkG2P5ByeUNERNFYbAOkNc/TaXO/Nhgf9r3PoatnXH6YSpEs4KdtjA==} + engines: {node: '>=20'} + '@pnpm/config.env-replace@1.1.0': resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} engines: {node: '>=12.22.0'} @@ -1437,6 +1477,94 @@ packages: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} + '@tailwindcss/node@4.1.18': + resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} + + '@tailwindcss/oxide-android-arm64@4.1.18': + resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.18': + resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.18': + resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.18': + resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==} + '@trysound/sax@0.2.0': resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} @@ -2344,6 +2472,10 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} @@ -2376,6 +2508,9 @@ packages: '@docusaurus/theme-common': ^3.6.0 webpack: ^5.0.0 + docusaurus2-dotenv@1.4.0: + resolution: {integrity: sha512-iWqem5fnBAyeBBtX75Fxp71uUAnwFaXzOmade8zAhN4vL3RG9m27sLSRwjJGVVgIkEo3esjGyCcTGTiCjfi+sg==} + dom-converter@0.2.0: resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==} @@ -2412,6 +2547,18 @@ packages: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} + dotenv-defaults@1.1.1: + resolution: {integrity: sha512-6fPRo9o/3MxKvmRZBD3oNFdxODdhJtIy1zcJeUSCs6HCy4tarUpd+G67UTU9tF6OWXeSPqsm4fPAB+2eY9Rt9Q==} + + dotenv-webpack@1.7.0: + resolution: {integrity: sha512-wwNtOBW/6gLQSkb8p43y0Wts970A3xtNiG/mpwj9MLUhtPCQG6i+/DSXXoNN7fbPCU/vQ7JjwGmgOeGZSSZnsw==} + peerDependencies: + webpack: ^1 || ^2 || ^3 || ^4 + + dotenv@6.2.0: + resolution: {integrity: sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==} + engines: {node: '>=6'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -2459,6 +2606,10 @@ packages: resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} + enhanced-resolve@5.18.4: + resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + engines: {node: '>=10.13.0'} + entities@2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} @@ -3171,6 +3322,10 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + joi@17.13.3: resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} @@ -3244,6 +3399,76 @@ packages: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -3299,6 +3524,9 @@ packages: lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + mark.js@8.11.1: resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} @@ -4300,10 +4528,70 @@ packages: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.4: - resolution: {integrity: sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + prettier-plugin-tailwindcss@0.7.2: + resolution: {integrity: sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==} + engines: {node: '>=20.19'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-hermes': '*' + '@prettier/plugin-oxc': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-svelte: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-hermes': + optional: true + '@prettier/plugin-oxc': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + '@zackad/prettier-plugin-twig': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-multiline-arrays: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-svelte: + optional: true + + prettier@3.7.4: + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} + engines: {node: '>=14'} + hasBin: true + pretty-error@4.0.0: resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==} @@ -4922,6 +5210,17 @@ packages: resolution: {integrity: sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==} hasBin: true + tailwind-merge@3.4.0: + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + + tailwindcss@4.1.18: + resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} @@ -5445,6 +5744,8 @@ snapshots: dependencies: '@algolia/client-common': 5.40.0 + '@alloc/quick-lru@5.2.0': {} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -6165,8 +6466,6 @@ snapshots: dependencies: core-js-pure: 3.42.0 - '@babel/runtime@7.28.3': {} - '@babel/runtime@7.28.6': {} '@babel/template@7.27.2': @@ -6235,224 +6534,224 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-cascade-layers@5.0.1(postcss@8.5.4)': + '@csstools/postcss-cascade-layers@5.0.1(postcss@8.5.6)': dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 7.1.0 - '@csstools/postcss-color-function@4.0.10(postcss@8.5.4)': + '@csstools/postcss-color-function@4.0.10(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.4) - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 - '@csstools/postcss-color-mix-function@3.0.10(postcss@8.5.4)': + '@csstools/postcss-color-mix-function@3.0.10(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.4) - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 - '@csstools/postcss-color-mix-variadic-function-arguments@1.0.0(postcss@8.5.4)': + '@csstools/postcss-color-mix-variadic-function-arguments@1.0.0(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.4) - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 - '@csstools/postcss-content-alt-text@2.0.6(postcss@8.5.4)': + '@csstools/postcss-content-alt-text@2.0.6(postcss@8.5.6)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.4) - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 - '@csstools/postcss-exponential-functions@2.0.9(postcss@8.5.4)': + '@csstools/postcss-exponential-functions@2.0.9(postcss@8.5.6)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.4 + postcss: 8.5.6 - '@csstools/postcss-font-format-keywords@4.0.0(postcss@8.5.4)': + '@csstools/postcss-font-format-keywords@4.0.0(postcss@8.5.6)': dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - '@csstools/postcss-gamut-mapping@2.0.10(postcss@8.5.4)': + '@csstools/postcss-gamut-mapping@2.0.10(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.4 + postcss: 8.5.6 - '@csstools/postcss-gradients-interpolation-method@5.0.10(postcss@8.5.4)': + '@csstools/postcss-gradients-interpolation-method@5.0.10(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.4) - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 - '@csstools/postcss-hwb-function@4.0.10(postcss@8.5.4)': + '@csstools/postcss-hwb-function@4.0.10(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.4) - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 - '@csstools/postcss-ic-unit@4.0.2(postcss@8.5.4)': + '@csstools/postcss-ic-unit@4.0.2(postcss@8.5.6)': dependencies: - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.4) - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - '@csstools/postcss-initial@2.0.1(postcss@8.5.4)': + '@csstools/postcss-initial@2.0.1(postcss@8.5.6)': dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - '@csstools/postcss-is-pseudo-class@5.0.1(postcss@8.5.4)': + '@csstools/postcss-is-pseudo-class@5.0.1(postcss@8.5.6)': dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 7.1.0 - '@csstools/postcss-light-dark-function@2.0.9(postcss@8.5.4)': + '@csstools/postcss-light-dark-function@2.0.9(postcss@8.5.6)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.4) - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 - '@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.5.4)': + '@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.5.6)': dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - '@csstools/postcss-logical-overflow@2.0.0(postcss@8.5.4)': + '@csstools/postcss-logical-overflow@2.0.0(postcss@8.5.6)': dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - '@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.5.4)': + '@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.5.6)': dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - '@csstools/postcss-logical-resize@3.0.0(postcss@8.5.4)': + '@csstools/postcss-logical-resize@3.0.0(postcss@8.5.6)': dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - '@csstools/postcss-logical-viewport-units@3.0.4(postcss@8.5.4)': + '@csstools/postcss-logical-viewport-units@3.0.4(postcss@8.5.6)': dependencies: '@csstools/css-tokenizer': 3.0.4 - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 - '@csstools/postcss-media-minmax@2.0.9(postcss@8.5.4)': + '@csstools/postcss-media-minmax@2.0.9(postcss@8.5.6)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - postcss: 8.5.4 + postcss: 8.5.6 - '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5(postcss@8.5.4)': + '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5(postcss@8.5.6)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - postcss: 8.5.4 + postcss: 8.5.6 - '@csstools/postcss-nested-calc@4.0.0(postcss@8.5.4)': + '@csstools/postcss-nested-calc@4.0.0(postcss@8.5.6)': dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - '@csstools/postcss-normalize-display-values@4.0.0(postcss@8.5.4)': + '@csstools/postcss-normalize-display-values@4.0.0(postcss@8.5.6)': dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - '@csstools/postcss-oklab-function@4.0.10(postcss@8.5.4)': + '@csstools/postcss-oklab-function@4.0.10(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.4) - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 - '@csstools/postcss-progressive-custom-properties@4.1.0(postcss@8.5.4)': + '@csstools/postcss-progressive-custom-properties@4.1.0(postcss@8.5.6)': dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - '@csstools/postcss-random-function@2.0.1(postcss@8.5.4)': + '@csstools/postcss-random-function@2.0.1(postcss@8.5.6)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.4 + postcss: 8.5.6 - '@csstools/postcss-relative-color-syntax@3.0.10(postcss@8.5.4)': + '@csstools/postcss-relative-color-syntax@3.0.10(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.4) - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 - '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.4)': + '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.6)': dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 7.1.0 - '@csstools/postcss-sign-functions@1.1.4(postcss@8.5.4)': + '@csstools/postcss-sign-functions@1.1.4(postcss@8.5.6)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.4 + postcss: 8.5.6 - '@csstools/postcss-stepped-value-functions@4.0.9(postcss@8.5.4)': + '@csstools/postcss-stepped-value-functions@4.0.9(postcss@8.5.6)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.4 + postcss: 8.5.6 - '@csstools/postcss-text-decoration-shorthand@4.0.2(postcss@8.5.4)': + '@csstools/postcss-text-decoration-shorthand@4.0.2(postcss@8.5.6)': dependencies: '@csstools/color-helpers': 5.0.2 - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - '@csstools/postcss-trigonometric-functions@4.0.9(postcss@8.5.4)': + '@csstools/postcss-trigonometric-functions@4.0.9(postcss@8.5.6)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.4 + postcss: 8.5.6 - '@csstools/postcss-unset-value@4.0.0(postcss@8.5.4)': + '@csstools/postcss-unset-value@4.0.0(postcss@8.5.6)': dependencies: - postcss: 8.5.4 + postcss: 8.5.6 '@csstools/selector-resolve-nested@3.0.0(postcss-selector-parser@7.1.0)': dependencies: @@ -6462,9 +6761,9 @@ snapshots: dependencies: postcss-selector-parser: 7.1.0 - '@csstools/utilities@2.0.0(postcss@8.5.4)': + '@csstools/utilities@2.0.0(postcss@8.5.6)': dependencies: - postcss: 8.5.4 + postcss: 8.5.6 '@discoveryjs/json-ext@0.5.7': {} @@ -6493,7 +6792,7 @@ snapshots: '@babel/preset-env': 7.27.2(@babel/core@7.27.1) '@babel/preset-react': 7.27.1(@babel/core@7.27.1) '@babel/preset-typescript': 7.27.1(@babel/core@7.27.1) - '@babel/runtime': 7.28.3 + '@babel/runtime': 7.28.6 '@babel/runtime-corejs3': 7.27.1 '@babel/traverse': 7.27.1 '@docusaurus/logger': 3.9.2 @@ -6524,14 +6823,14 @@ snapshots: copy-webpack-plugin: 11.0.0(webpack@5.99.8) css-loader: 6.11.0(webpack@5.99.8) css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.99.8) - cssnano: 6.1.2(postcss@8.5.4) + cssnano: 6.1.2(postcss@8.5.6) file-loader: 6.2.0(webpack@5.99.8) html-minifier-terser: 7.2.0 mini-css-extract-plugin: 2.9.2(webpack@5.99.8) null-loader: 4.0.1(webpack@5.99.8) - postcss: 8.5.4 - postcss-loader: 7.3.4(postcss@8.5.4)(typescript@5.8.2)(webpack@5.99.8) - postcss-preset-env: 10.2.1(postcss@8.5.4) + postcss: 8.5.6 + postcss-loader: 7.3.4(postcss@8.5.6)(typescript@5.8.2)(webpack@5.99.8) + postcss-preset-env: 10.2.1(postcss@8.5.6) terser-webpack-plugin: 5.3.14(webpack@5.99.8) tslib: 2.8.1 url-loader: 4.1.1(file-loader@6.2.0(webpack@5.99.8))(webpack@5.99.8) @@ -6620,9 +6919,9 @@ snapshots: '@docusaurus/cssnano-preset@3.9.2': dependencies: - cssnano-preset-advanced: 6.1.2(postcss@8.5.4) - postcss: 8.5.4 - postcss-sort-media-queries: 5.2.0(postcss@8.5.4) + cssnano-preset-advanced: 6.1.2(postcss@8.5.6) + postcss: 8.5.6 + postcss-sort-media-queries: 5.2.0(postcss@8.5.6) tslib: 2.8.1 '@docusaurus/logger@3.7.0': @@ -7072,7 +7371,7 @@ snapshots: infima: 0.2.0-alpha.45 lodash: 4.17.21 nprogress: 0.2.0 - postcss: 8.5.4 + postcss: 8.5.6 prism-react-renderer: 2.4.1(react@19.0.0) prismjs: 1.30.0 react: 19.0.0 @@ -7402,6 +7701,11 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/set-array@1.2.1': {} @@ -7413,6 +7717,8 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -7670,6 +7976,75 @@ snapshots: dependencies: defer-to-connect: 2.0.1 + '@tailwindcss/node@4.1.18': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.4 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.18 + + '@tailwindcss/oxide-android-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide@4.1.18': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-x64': 4.1.18 + '@tailwindcss/oxide-freebsd-x64': 4.1.18 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-x64-musl': 4.1.18 + '@tailwindcss/oxide-wasm32-wasi': 4.1.18 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 + + '@tailwindcss/postcss@4.1.18': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.18 + '@tailwindcss/oxide': 4.1.18 + postcss: 8.5.6 + tailwindcss: 4.1.18 + '@trysound/sax@0.2.0': {} '@types/body-parser@1.19.5': @@ -8050,14 +8425,14 @@ snapshots: astring@1.9.0: {} - autoprefixer@10.4.21(postcss@8.5.4): + autoprefixer@10.4.21(postcss@8.5.6): dependencies: browserslist: 4.25.0 caniuse-lite: 1.0.30001761 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 babel-loader@9.2.1(@babel/core@7.27.1)(webpack@5.99.8): @@ -8451,32 +8826,32 @@ snapshots: dependencies: type-fest: 1.4.0 - css-blank-pseudo@7.0.1(postcss@8.5.4): + css-blank-pseudo@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 7.1.0 css-color-keywords@1.0.0: {} - css-declaration-sorter@7.2.0(postcss@8.5.4): + css-declaration-sorter@7.2.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - css-has-pseudo@7.0.2(postcss@8.5.4): + css-has-pseudo@7.0.2(postcss@8.5.6): dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 css-loader@6.11.0(webpack@5.99.8): dependencies: - icss-utils: 5.1.0(postcss@8.5.4) - postcss: 8.5.4 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.4) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.4) - postcss-modules-scope: 3.2.1(postcss@8.5.4) - postcss-modules-values: 4.0.0(postcss@8.5.4) + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.6) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.6) + postcss-modules-scope: 3.2.1(postcss@8.5.6) + postcss-modules-values: 4.0.0(postcss@8.5.6) postcss-value-parser: 4.2.0 semver: 7.7.2 optionalDependencies: @@ -8485,18 +8860,18 @@ snapshots: css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.99.8): dependencies: '@jridgewell/trace-mapping': 0.3.25 - cssnano: 6.1.2(postcss@8.5.4) + cssnano: 6.1.2(postcss@8.5.6) jest-worker: 29.7.0 - postcss: 8.5.4 + postcss: 8.5.6 schema-utils: 4.3.2 serialize-javascript: 6.0.2 webpack: 5.99.8 optionalDependencies: clean-css: 5.3.3 - css-prefers-color-scheme@10.0.0(postcss@8.5.4): + css-prefers-color-scheme@10.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 css-select@4.3.0: dependencies: @@ -8536,60 +8911,60 @@ snapshots: cssesc@3.0.0: {} - cssnano-preset-advanced@6.1.2(postcss@8.5.4): + cssnano-preset-advanced@6.1.2(postcss@8.5.6): dependencies: - autoprefixer: 10.4.21(postcss@8.5.4) + autoprefixer: 10.4.21(postcss@8.5.6) browserslist: 4.25.0 - cssnano-preset-default: 6.1.2(postcss@8.5.4) - postcss: 8.5.4 - postcss-discard-unused: 6.0.5(postcss@8.5.4) - postcss-merge-idents: 6.0.3(postcss@8.5.4) - postcss-reduce-idents: 6.0.3(postcss@8.5.4) - postcss-zindex: 6.0.2(postcss@8.5.4) + cssnano-preset-default: 6.1.2(postcss@8.5.6) + postcss: 8.5.6 + postcss-discard-unused: 6.0.5(postcss@8.5.6) + postcss-merge-idents: 6.0.3(postcss@8.5.6) + postcss-reduce-idents: 6.0.3(postcss@8.5.6) + postcss-zindex: 6.0.2(postcss@8.5.6) - cssnano-preset-default@6.1.2(postcss@8.5.4): + cssnano-preset-default@6.1.2(postcss@8.5.6): dependencies: browserslist: 4.25.0 - css-declaration-sorter: 7.2.0(postcss@8.5.4) - cssnano-utils: 4.0.2(postcss@8.5.4) - postcss: 8.5.4 - postcss-calc: 9.0.1(postcss@8.5.4) - postcss-colormin: 6.1.0(postcss@8.5.4) - postcss-convert-values: 6.1.0(postcss@8.5.4) - postcss-discard-comments: 6.0.2(postcss@8.5.4) - postcss-discard-duplicates: 6.0.3(postcss@8.5.4) - postcss-discard-empty: 6.0.3(postcss@8.5.4) - postcss-discard-overridden: 6.0.2(postcss@8.5.4) - postcss-merge-longhand: 6.0.5(postcss@8.5.4) - postcss-merge-rules: 6.1.1(postcss@8.5.4) - postcss-minify-font-values: 6.1.0(postcss@8.5.4) - postcss-minify-gradients: 6.0.3(postcss@8.5.4) - postcss-minify-params: 6.1.0(postcss@8.5.4) - postcss-minify-selectors: 6.0.4(postcss@8.5.4) - postcss-normalize-charset: 6.0.2(postcss@8.5.4) - postcss-normalize-display-values: 6.0.2(postcss@8.5.4) - postcss-normalize-positions: 6.0.2(postcss@8.5.4) - postcss-normalize-repeat-style: 6.0.2(postcss@8.5.4) - postcss-normalize-string: 6.0.2(postcss@8.5.4) - postcss-normalize-timing-functions: 6.0.2(postcss@8.5.4) - postcss-normalize-unicode: 6.1.0(postcss@8.5.4) - postcss-normalize-url: 6.0.2(postcss@8.5.4) - postcss-normalize-whitespace: 6.0.2(postcss@8.5.4) - postcss-ordered-values: 6.0.2(postcss@8.5.4) - postcss-reduce-initial: 6.1.0(postcss@8.5.4) - postcss-reduce-transforms: 6.0.2(postcss@8.5.4) - postcss-svgo: 6.0.3(postcss@8.5.4) - postcss-unique-selectors: 6.0.4(postcss@8.5.4) - - cssnano-utils@4.0.2(postcss@8.5.4): - dependencies: - postcss: 8.5.4 - - cssnano@6.1.2(postcss@8.5.4): - dependencies: - cssnano-preset-default: 6.1.2(postcss@8.5.4) + css-declaration-sorter: 7.2.0(postcss@8.5.6) + cssnano-utils: 4.0.2(postcss@8.5.6) + postcss: 8.5.6 + postcss-calc: 9.0.1(postcss@8.5.6) + postcss-colormin: 6.1.0(postcss@8.5.6) + postcss-convert-values: 6.1.0(postcss@8.5.6) + postcss-discard-comments: 6.0.2(postcss@8.5.6) + postcss-discard-duplicates: 6.0.3(postcss@8.5.6) + postcss-discard-empty: 6.0.3(postcss@8.5.6) + postcss-discard-overridden: 6.0.2(postcss@8.5.6) + postcss-merge-longhand: 6.0.5(postcss@8.5.6) + postcss-merge-rules: 6.1.1(postcss@8.5.6) + postcss-minify-font-values: 6.1.0(postcss@8.5.6) + postcss-minify-gradients: 6.0.3(postcss@8.5.6) + postcss-minify-params: 6.1.0(postcss@8.5.6) + postcss-minify-selectors: 6.0.4(postcss@8.5.6) + postcss-normalize-charset: 6.0.2(postcss@8.5.6) + postcss-normalize-display-values: 6.0.2(postcss@8.5.6) + postcss-normalize-positions: 6.0.2(postcss@8.5.6) + postcss-normalize-repeat-style: 6.0.2(postcss@8.5.6) + postcss-normalize-string: 6.0.2(postcss@8.5.6) + postcss-normalize-timing-functions: 6.0.2(postcss@8.5.6) + postcss-normalize-unicode: 6.1.0(postcss@8.5.6) + postcss-normalize-url: 6.0.2(postcss@8.5.6) + postcss-normalize-whitespace: 6.0.2(postcss@8.5.6) + postcss-ordered-values: 6.0.2(postcss@8.5.6) + postcss-reduce-initial: 6.1.0(postcss@8.5.6) + postcss-reduce-transforms: 6.0.2(postcss@8.5.6) + postcss-svgo: 6.0.3(postcss@8.5.6) + postcss-unique-selectors: 6.0.4(postcss@8.5.6) + + cssnano-utils@4.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + cssnano@6.1.2(postcss@8.5.6): + dependencies: + cssnano-preset-default: 6.1.2(postcss@8.5.6) lilconfig: 3.1.3 - postcss: 8.5.4 + postcss: 8.5.6 csso@5.0.5: dependencies: @@ -8656,6 +9031,8 @@ snapshots: destroy@1.2.0: {} + detect-libc@2.1.2: {} + detect-node@2.1.0: {} detect-port@1.6.1: @@ -8699,8 +9076,8 @@ snapshots: clsx: 1.2.1 lodash: 4.17.21 mobx: 6.13.7 - postcss: 8.5.4 - postcss-prefix-selector: 1.16.1(postcss@8.5.4) + postcss: 8.5.6 + postcss-prefix-selector: 1.16.1(postcss@8.5.6) redoc: 2.4.0(core-js@3.42.0)(mobx@6.13.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(styled-components@6.1.18(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) styled-components: 6.1.18(react-dom@19.0.0(react@19.0.0))(react@19.0.0) webpack: 5.99.8 @@ -8712,6 +9089,12 @@ snapshots: - react-native - supports-color + docusaurus2-dotenv@1.4.0(webpack@5.99.8): + dependencies: + dotenv-webpack: 1.7.0(webpack@5.99.8) + transitivePeerDependencies: + - webpack + dom-converter@0.2.0: dependencies: utila: 0.4.0 @@ -8763,6 +9146,17 @@ snapshots: dependencies: is-obj: 2.0.0 + dotenv-defaults@1.1.1: + dependencies: + dotenv: 6.2.0 + + dotenv-webpack@1.7.0(webpack@5.99.8): + dependencies: + dotenv-defaults: 1.1.1 + webpack: 5.99.8 + + dotenv@6.2.0: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -8798,6 +9192,11 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.1 + enhanced-resolve@5.18.4: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + entities@2.2.0: {} entities@4.5.0: {} @@ -9420,9 +9819,9 @@ snapshots: dependencies: safer-buffer: 2.1.2 - icss-utils@5.1.0(postcss@8.5.4): + icss-utils@5.1.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 ignore@5.3.2: {} @@ -9582,6 +9981,8 @@ snapshots: jiti@1.21.7: {} + jiti@2.6.1: {} + joi@17.13.3: dependencies: '@hapi/hoek': 9.3.0 @@ -9646,6 +10047,55 @@ snapshots: leven@3.1.0: {} + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -9690,6 +10140,10 @@ snapshots: lunr@2.3.9: {} + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + mark.js@8.11.1: {} markdown-extensions@2.0.0: {} @@ -10557,404 +11011,404 @@ snapshots: dependencies: '@babel/runtime': 7.28.6 - postcss-attribute-case-insensitive@7.0.1(postcss@8.5.4): + postcss-attribute-case-insensitive@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 7.1.0 - postcss-calc@9.0.1(postcss@8.5.4): + postcss-calc@9.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 6.1.2 postcss-value-parser: 4.2.0 - postcss-clamp@4.1.0(postcss@8.5.4): + postcss-clamp@4.1.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-color-functional-notation@7.0.10(postcss@8.5.4): + postcss-color-functional-notation@7.0.10(postcss@8.5.6): dependencies: '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.4) - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 - postcss-color-hex-alpha@10.0.0(postcss@8.5.4): + postcss-color-hex-alpha@10.0.0(postcss@8.5.6): dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-color-rebeccapurple@10.0.0(postcss@8.5.4): + postcss-color-rebeccapurple@10.0.0(postcss@8.5.6): dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-colormin@6.1.0(postcss@8.5.4): + postcss-colormin@6.1.0(postcss@8.5.6): dependencies: browserslist: 4.25.0 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-convert-values@6.1.0(postcss@8.5.4): + postcss-convert-values@6.1.0(postcss@8.5.6): dependencies: browserslist: 4.25.0 - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-custom-media@11.0.6(postcss@8.5.4): + postcss-custom-media@11.0.6(postcss@8.5.6): dependencies: '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - postcss: 8.5.4 + postcss: 8.5.6 - postcss-custom-properties@14.0.6(postcss@8.5.4): + postcss-custom-properties@14.0.6(postcss@8.5.6): dependencies: '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-custom-selectors@8.0.5(postcss@8.5.4): + postcss-custom-selectors@8.0.5(postcss@8.5.6): dependencies: '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 7.1.0 - postcss-dir-pseudo-class@9.0.1(postcss@8.5.4): + postcss-dir-pseudo-class@9.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 7.1.0 - postcss-discard-comments@6.0.2(postcss@8.5.4): + postcss-discard-comments@6.0.2(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-discard-duplicates@6.0.3(postcss@8.5.4): + postcss-discard-duplicates@6.0.3(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-discard-empty@6.0.3(postcss@8.5.4): + postcss-discard-empty@6.0.3(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-discard-overridden@6.0.2(postcss@8.5.4): + postcss-discard-overridden@6.0.2(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-discard-unused@6.0.5(postcss@8.5.4): + postcss-discard-unused@6.0.5(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 6.1.2 - postcss-double-position-gradients@6.0.2(postcss@8.5.4): + postcss-double-position-gradients@6.0.2(postcss@8.5.6): dependencies: - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.4) - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-focus-visible@10.0.1(postcss@8.5.4): + postcss-focus-visible@10.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 7.1.0 - postcss-focus-within@9.0.1(postcss@8.5.4): + postcss-focus-within@9.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 7.1.0 - postcss-font-variant@5.0.0(postcss@8.5.4): + postcss-font-variant@5.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-gap-properties@6.0.0(postcss@8.5.4): + postcss-gap-properties@6.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-image-set-function@7.0.0(postcss@8.5.4): + postcss-image-set-function@7.0.0(postcss@8.5.6): dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-lab-function@7.0.10(postcss@8.5.4): + postcss-lab-function@7.0.10(postcss@8.5.6): dependencies: '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.4) - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 - postcss-loader@7.3.4(postcss@8.5.4)(typescript@5.8.2)(webpack@5.99.8): + postcss-loader@7.3.4(postcss@8.5.6)(typescript@5.8.2)(webpack@5.99.8): dependencies: cosmiconfig: 8.3.6(typescript@5.8.2) jiti: 1.21.7 - postcss: 8.5.4 + postcss: 8.5.6 semver: 7.7.2 webpack: 5.99.8 transitivePeerDependencies: - typescript - postcss-logical@8.1.0(postcss@8.5.4): + postcss-logical@8.1.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-merge-idents@6.0.3(postcss@8.5.4): + postcss-merge-idents@6.0.3(postcss@8.5.6): dependencies: - cssnano-utils: 4.0.2(postcss@8.5.4) - postcss: 8.5.4 + cssnano-utils: 4.0.2(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-merge-longhand@6.0.5(postcss@8.5.4): + postcss-merge-longhand@6.0.5(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - stylehacks: 6.1.1(postcss@8.5.4) + stylehacks: 6.1.1(postcss@8.5.6) - postcss-merge-rules@6.1.1(postcss@8.5.4): + postcss-merge-rules@6.1.1(postcss@8.5.6): dependencies: browserslist: 4.25.0 caniuse-api: 3.0.0 - cssnano-utils: 4.0.2(postcss@8.5.4) - postcss: 8.5.4 + cssnano-utils: 4.0.2(postcss@8.5.6) + postcss: 8.5.6 postcss-selector-parser: 6.1.2 - postcss-minify-font-values@6.1.0(postcss@8.5.4): + postcss-minify-font-values@6.1.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-minify-gradients@6.0.3(postcss@8.5.4): + postcss-minify-gradients@6.0.3(postcss@8.5.6): dependencies: colord: 2.9.3 - cssnano-utils: 4.0.2(postcss@8.5.4) - postcss: 8.5.4 + cssnano-utils: 4.0.2(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-minify-params@6.1.0(postcss@8.5.4): + postcss-minify-params@6.1.0(postcss@8.5.6): dependencies: browserslist: 4.25.0 - cssnano-utils: 4.0.2(postcss@8.5.4) - postcss: 8.5.4 + cssnano-utils: 4.0.2(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-minify-selectors@6.0.4(postcss@8.5.4): + postcss-minify-selectors@6.0.4(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 6.1.2 - postcss-modules-extract-imports@3.1.0(postcss@8.5.4): + postcss-modules-extract-imports@3.1.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-modules-local-by-default@4.2.0(postcss@8.5.4): + postcss-modules-local-by-default@4.2.0(postcss@8.5.6): dependencies: - icss-utils: 5.1.0(postcss@8.5.4) - postcss: 8.5.4 + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.1(postcss@8.5.4): + postcss-modules-scope@3.2.1(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 7.1.0 - postcss-modules-values@4.0.0(postcss@8.5.4): + postcss-modules-values@4.0.0(postcss@8.5.6): dependencies: - icss-utils: 5.1.0(postcss@8.5.4) - postcss: 8.5.4 + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 - postcss-nesting@13.0.1(postcss@8.5.4): + postcss-nesting@13.0.1(postcss@8.5.6): dependencies: '@csstools/selector-resolve-nested': 3.0.0(postcss-selector-parser@7.1.0) '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 7.1.0 - postcss-normalize-charset@6.0.2(postcss@8.5.4): + postcss-normalize-charset@6.0.2(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-normalize-display-values@6.0.2(postcss@8.5.4): + postcss-normalize-display-values@6.0.2(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-positions@6.0.2(postcss@8.5.4): + postcss-normalize-positions@6.0.2(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-repeat-style@6.0.2(postcss@8.5.4): + postcss-normalize-repeat-style@6.0.2(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-string@6.0.2(postcss@8.5.4): + postcss-normalize-string@6.0.2(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-timing-functions@6.0.2(postcss@8.5.4): + postcss-normalize-timing-functions@6.0.2(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-unicode@6.1.0(postcss@8.5.4): + postcss-normalize-unicode@6.1.0(postcss@8.5.6): dependencies: browserslist: 4.25.0 - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-url@6.0.2(postcss@8.5.4): + postcss-normalize-url@6.0.2(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-whitespace@6.0.2(postcss@8.5.4): + postcss-normalize-whitespace@6.0.2(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-opacity-percentage@3.0.0(postcss@8.5.4): + postcss-opacity-percentage@3.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-ordered-values@6.0.2(postcss@8.5.4): + postcss-ordered-values@6.0.2(postcss@8.5.6): dependencies: - cssnano-utils: 4.0.2(postcss@8.5.4) - postcss: 8.5.4 + cssnano-utils: 4.0.2(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-overflow-shorthand@6.0.0(postcss@8.5.4): + postcss-overflow-shorthand@6.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-page-break@3.0.4(postcss@8.5.4): + postcss-page-break@3.0.4(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-place@10.0.0(postcss@8.5.4): + postcss-place@10.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-prefix-selector@1.16.1(postcss@8.5.4): - dependencies: - postcss: 8.5.4 - - postcss-preset-env@10.2.1(postcss@8.5.4): - dependencies: - '@csstools/postcss-cascade-layers': 5.0.1(postcss@8.5.4) - '@csstools/postcss-color-function': 4.0.10(postcss@8.5.4) - '@csstools/postcss-color-mix-function': 3.0.10(postcss@8.5.4) - '@csstools/postcss-color-mix-variadic-function-arguments': 1.0.0(postcss@8.5.4) - '@csstools/postcss-content-alt-text': 2.0.6(postcss@8.5.4) - '@csstools/postcss-exponential-functions': 2.0.9(postcss@8.5.4) - '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.5.4) - '@csstools/postcss-gamut-mapping': 2.0.10(postcss@8.5.4) - '@csstools/postcss-gradients-interpolation-method': 5.0.10(postcss@8.5.4) - '@csstools/postcss-hwb-function': 4.0.10(postcss@8.5.4) - '@csstools/postcss-ic-unit': 4.0.2(postcss@8.5.4) - '@csstools/postcss-initial': 2.0.1(postcss@8.5.4) - '@csstools/postcss-is-pseudo-class': 5.0.1(postcss@8.5.4) - '@csstools/postcss-light-dark-function': 2.0.9(postcss@8.5.4) - '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.5.4) - '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.5.4) - '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.5.4) - '@csstools/postcss-logical-resize': 3.0.0(postcss@8.5.4) - '@csstools/postcss-logical-viewport-units': 3.0.4(postcss@8.5.4) - '@csstools/postcss-media-minmax': 2.0.9(postcss@8.5.4) - '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.5(postcss@8.5.4) - '@csstools/postcss-nested-calc': 4.0.0(postcss@8.5.4) - '@csstools/postcss-normalize-display-values': 4.0.0(postcss@8.5.4) - '@csstools/postcss-oklab-function': 4.0.10(postcss@8.5.4) - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.4) - '@csstools/postcss-random-function': 2.0.1(postcss@8.5.4) - '@csstools/postcss-relative-color-syntax': 3.0.10(postcss@8.5.4) - '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.5.4) - '@csstools/postcss-sign-functions': 1.1.4(postcss@8.5.4) - '@csstools/postcss-stepped-value-functions': 4.0.9(postcss@8.5.4) - '@csstools/postcss-text-decoration-shorthand': 4.0.2(postcss@8.5.4) - '@csstools/postcss-trigonometric-functions': 4.0.9(postcss@8.5.4) - '@csstools/postcss-unset-value': 4.0.0(postcss@8.5.4) - autoprefixer: 10.4.21(postcss@8.5.4) + postcss-prefix-selector@1.16.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-preset-env@10.2.1(postcss@8.5.6): + dependencies: + '@csstools/postcss-cascade-layers': 5.0.1(postcss@8.5.6) + '@csstools/postcss-color-function': 4.0.10(postcss@8.5.6) + '@csstools/postcss-color-mix-function': 3.0.10(postcss@8.5.6) + '@csstools/postcss-color-mix-variadic-function-arguments': 1.0.0(postcss@8.5.6) + '@csstools/postcss-content-alt-text': 2.0.6(postcss@8.5.6) + '@csstools/postcss-exponential-functions': 2.0.9(postcss@8.5.6) + '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.5.6) + '@csstools/postcss-gamut-mapping': 2.0.10(postcss@8.5.6) + '@csstools/postcss-gradients-interpolation-method': 5.0.10(postcss@8.5.6) + '@csstools/postcss-hwb-function': 4.0.10(postcss@8.5.6) + '@csstools/postcss-ic-unit': 4.0.2(postcss@8.5.6) + '@csstools/postcss-initial': 2.0.1(postcss@8.5.6) + '@csstools/postcss-is-pseudo-class': 5.0.1(postcss@8.5.6) + '@csstools/postcss-light-dark-function': 2.0.9(postcss@8.5.6) + '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.5.6) + '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.5.6) + '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.5.6) + '@csstools/postcss-logical-resize': 3.0.0(postcss@8.5.6) + '@csstools/postcss-logical-viewport-units': 3.0.4(postcss@8.5.6) + '@csstools/postcss-media-minmax': 2.0.9(postcss@8.5.6) + '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.5(postcss@8.5.6) + '@csstools/postcss-nested-calc': 4.0.0(postcss@8.5.6) + '@csstools/postcss-normalize-display-values': 4.0.0(postcss@8.5.6) + '@csstools/postcss-oklab-function': 4.0.10(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/postcss-random-function': 2.0.1(postcss@8.5.6) + '@csstools/postcss-relative-color-syntax': 3.0.10(postcss@8.5.6) + '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.5.6) + '@csstools/postcss-sign-functions': 1.1.4(postcss@8.5.6) + '@csstools/postcss-stepped-value-functions': 4.0.9(postcss@8.5.6) + '@csstools/postcss-text-decoration-shorthand': 4.0.2(postcss@8.5.6) + '@csstools/postcss-trigonometric-functions': 4.0.9(postcss@8.5.6) + '@csstools/postcss-unset-value': 4.0.0(postcss@8.5.6) + autoprefixer: 10.4.21(postcss@8.5.6) browserslist: 4.25.0 - css-blank-pseudo: 7.0.1(postcss@8.5.4) - css-has-pseudo: 7.0.2(postcss@8.5.4) - css-prefers-color-scheme: 10.0.0(postcss@8.5.4) + css-blank-pseudo: 7.0.1(postcss@8.5.6) + css-has-pseudo: 7.0.2(postcss@8.5.6) + css-prefers-color-scheme: 10.0.0(postcss@8.5.6) cssdb: 8.3.0 - postcss: 8.5.4 - postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.4) - postcss-clamp: 4.1.0(postcss@8.5.4) - postcss-color-functional-notation: 7.0.10(postcss@8.5.4) - postcss-color-hex-alpha: 10.0.0(postcss@8.5.4) - postcss-color-rebeccapurple: 10.0.0(postcss@8.5.4) - postcss-custom-media: 11.0.6(postcss@8.5.4) - postcss-custom-properties: 14.0.6(postcss@8.5.4) - postcss-custom-selectors: 8.0.5(postcss@8.5.4) - postcss-dir-pseudo-class: 9.0.1(postcss@8.5.4) - postcss-double-position-gradients: 6.0.2(postcss@8.5.4) - postcss-focus-visible: 10.0.1(postcss@8.5.4) - postcss-focus-within: 9.0.1(postcss@8.5.4) - postcss-font-variant: 5.0.0(postcss@8.5.4) - postcss-gap-properties: 6.0.0(postcss@8.5.4) - postcss-image-set-function: 7.0.0(postcss@8.5.4) - postcss-lab-function: 7.0.10(postcss@8.5.4) - postcss-logical: 8.1.0(postcss@8.5.4) - postcss-nesting: 13.0.1(postcss@8.5.4) - postcss-opacity-percentage: 3.0.0(postcss@8.5.4) - postcss-overflow-shorthand: 6.0.0(postcss@8.5.4) - postcss-page-break: 3.0.4(postcss@8.5.4) - postcss-place: 10.0.0(postcss@8.5.4) - postcss-pseudo-class-any-link: 10.0.1(postcss@8.5.4) - postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.4) - postcss-selector-not: 8.0.1(postcss@8.5.4) - - postcss-pseudo-class-any-link@10.0.1(postcss@8.5.4): - dependencies: - postcss: 8.5.4 + postcss: 8.5.6 + postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.6) + postcss-clamp: 4.1.0(postcss@8.5.6) + postcss-color-functional-notation: 7.0.10(postcss@8.5.6) + postcss-color-hex-alpha: 10.0.0(postcss@8.5.6) + postcss-color-rebeccapurple: 10.0.0(postcss@8.5.6) + postcss-custom-media: 11.0.6(postcss@8.5.6) + postcss-custom-properties: 14.0.6(postcss@8.5.6) + postcss-custom-selectors: 8.0.5(postcss@8.5.6) + postcss-dir-pseudo-class: 9.0.1(postcss@8.5.6) + postcss-double-position-gradients: 6.0.2(postcss@8.5.6) + postcss-focus-visible: 10.0.1(postcss@8.5.6) + postcss-focus-within: 9.0.1(postcss@8.5.6) + postcss-font-variant: 5.0.0(postcss@8.5.6) + postcss-gap-properties: 6.0.0(postcss@8.5.6) + postcss-image-set-function: 7.0.0(postcss@8.5.6) + postcss-lab-function: 7.0.10(postcss@8.5.6) + postcss-logical: 8.1.0(postcss@8.5.6) + postcss-nesting: 13.0.1(postcss@8.5.6) + postcss-opacity-percentage: 3.0.0(postcss@8.5.6) + postcss-overflow-shorthand: 6.0.0(postcss@8.5.6) + postcss-page-break: 3.0.4(postcss@8.5.6) + postcss-place: 10.0.0(postcss@8.5.6) + postcss-pseudo-class-any-link: 10.0.1(postcss@8.5.6) + postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.6) + postcss-selector-not: 8.0.1(postcss@8.5.6) + + postcss-pseudo-class-any-link@10.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 postcss-selector-parser: 7.1.0 - postcss-reduce-idents@6.0.3(postcss@8.5.4): + postcss-reduce-idents@6.0.3(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-reduce-initial@6.1.0(postcss@8.5.4): + postcss-reduce-initial@6.1.0(postcss@8.5.6): dependencies: browserslist: 4.25.0 caniuse-api: 3.0.0 - postcss: 8.5.4 + postcss: 8.5.6 - postcss-reduce-transforms@6.0.2(postcss@8.5.4): + postcss-reduce-transforms@6.0.2(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-replace-overflow-wrap@4.0.0(postcss@8.5.4): + postcss-replace-overflow-wrap@4.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-selector-not@8.0.1(postcss@8.5.4): + postcss-selector-not@8.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 7.1.0 postcss-selector-parser@6.1.2: @@ -10967,27 +11421,27 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-sort-media-queries@5.2.0(postcss@8.5.4): + postcss-sort-media-queries@5.2.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 sort-css-media-queries: 2.2.0 - postcss-svgo@6.0.3(postcss@8.5.4): + postcss-svgo@6.0.3(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 svgo: 3.3.2 - postcss-unique-selectors@6.0.4(postcss@8.5.4): + postcss-unique-selectors@6.0.4(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 6.1.2 postcss-value-parser@4.2.0: {} - postcss-zindex@6.0.2(postcss@8.5.4): + postcss-zindex@6.0.2(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss@8.4.49: dependencies: @@ -10995,12 +11449,18 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.4: + postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 + prettier-plugin-tailwindcss@0.7.2(prettier@3.7.4): + dependencies: + prettier: 3.7.4 + + prettier@3.7.4: {} + pretty-error@4.0.0: dependencies: lodash: 4.17.21 @@ -11091,19 +11551,19 @@ snapshots: react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@6.0.0(react@19.0.0))(webpack@5.99.8): dependencies: - '@babel/runtime': 7.28.3 + '@babel/runtime': 7.28.6 react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.0.0)' webpack: 5.99.8 react-router-config@5.1.1(react-router@5.3.4(react@19.0.0))(react@19.0.0): dependencies: - '@babel/runtime': 7.28.3 + '@babel/runtime': 7.28.6 react: 19.0.0 react-router: 5.3.4(react@19.0.0) react-router-dom@5.3.4(react@19.0.0): dependencies: - '@babel/runtime': 7.28.3 + '@babel/runtime': 7.28.6 history: 4.10.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -11114,7 +11574,7 @@ snapshots: react-router@5.3.4(react@19.0.0): dependencies: - '@babel/runtime': 7.28.3 + '@babel/runtime': 7.28.6 history: 4.10.1 hoist-non-react-statics: 3.3.2 loose-envify: 1.4.0 @@ -11393,7 +11853,7 @@ snapshots: dependencies: escalade: 3.2.0 picocolors: 1.1.1 - postcss: 8.5.4 + postcss: 8.5.6 strip-json-comments: 3.1.1 run-applescript@7.1.0: {} @@ -11751,10 +12211,10 @@ snapshots: stylis: 4.3.2 tslib: 2.6.2 - stylehacks@6.1.1(postcss@8.5.4): + stylehacks@6.1.1(postcss@8.5.6): dependencies: browserslist: 4.25.0 - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 6.1.2 stylis@4.3.2: {} @@ -11797,6 +12257,14 @@ snapshots: transitivePeerDependencies: - encoding + tailwind-merge@3.4.0: {} + + tailwindcss-animate@1.0.7(tailwindcss@4.1.18): + dependencies: + tailwindcss: 4.1.18 + + tailwindcss@4.1.18: {} + tapable@2.2.1: {} tar@7.5.4: diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 000000000..483f37854 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; diff --git a/src/components/Pricing/PriceAmount.js b/src/components/Pricing/PriceAmount.js new file mode 100644 index 000000000..2c0a71303 --- /dev/null +++ b/src/components/Pricing/PriceAmount.js @@ -0,0 +1,33 @@ +import { cn } from "@site/src/lib/utils"; +import { Skeleton } from "@site/src/components/ui/Skeleton"; + +// interface Props { +// loading: boolean; +// tier: Tier; +// priceMap: Record; +// value: string; +// priceSuffix: string; +// } + +export function PriceAmount({ loading, priceMap, priceSuffix, tier }) { + return ( +
+ {loading ? ( + + ) : ( + <> +
+ {priceMap[tier.priceId].replace(/\.00$/, "")} +
+
+ {priceSuffix} +
+ + )} +
+ ); +} diff --git a/src/components/Pricing/PriceCards.js b/src/components/Pricing/PriceCards.js new file mode 100644 index 000000000..b9a5a9bb6 --- /dev/null +++ b/src/components/Pricing/PriceCards.js @@ -0,0 +1,35 @@ +import { PricingTier } from "@site/src/constants/pricing-tier"; +import { PriceTitle } from "@site/src/components/Pricing/PriceTitle"; +import { PriceAmount } from "@site/src/components/Pricing/PriceAmount"; +import { cn } from "@site/src/lib/utils"; + +export function PriceCards({ loading, priceMap }) { + return ( +
+ {PricingTier.map((tier) => ( +
+
+ {/* {tier.featured && } */} + + + {/*
+ +
*/} +
{tier.description}
+
+
+ {/* */} +
+ {/* */} +
+ ))} +
+ ); +} diff --git a/src/components/Pricing/PriceTitle.js b/src/components/Pricing/PriceTitle.js new file mode 100644 index 000000000..7341e2f19 --- /dev/null +++ b/src/components/Pricing/PriceTitle.js @@ -0,0 +1,33 @@ +// import { Tier } from "@site/src/constants/pricing-tier"; +import { cn } from "@site/src/lib/utils"; + +export function PriceTitle({ tier }) { + const { name, featured, icon } = tier; + return ( +
+
+
+ {icon} +
+

{name}

+
+ {featured && ( +
+ Most popular +
+ )} +
+ ); +} diff --git a/src/components/Pricing/index.js b/src/components/Pricing/index.js new file mode 100644 index 000000000..24627f3b7 --- /dev/null +++ b/src/components/Pricing/index.js @@ -0,0 +1,36 @@ +import { useEffect, useState } from "react"; +import { initializePaddle } from "@paddle/paddle-js"; +import { usePaddlePrices } from "@site/src/hooks/usePaddlePrices"; +import { PriceCards } from "@site/src/components/Pricing/PriceCards"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; + +// Insipred by https://github.com/PaddleHQ/paddle-nextjs-starter-kit + +export function Pricing({ country }) { + console.log("Rendering Pricing component", country); + const [paddle, setPaddle] = useState(undefined); + const { prices, loading } = usePaddlePrices(paddle, country); + const { i18n } = useDocusaurusContext(); + + console.log("i18n locale:", i18n.currentLocale); + + useEffect(() => { + if (process.env.PADDLE_CLIENT_TOKEN && process.env.PADDLE_ENV) { + initializePaddle({ + token: process.env.PADDLE_CLIENT_TOKEN, + environment: process.env.PADDLE_ENV, + }).then((paddle) => { + if (paddle) { + setPaddle(paddle); + console.log("Paddle initialized"); + } + }); + } + }, []); + + return ( +
+ +
+ ); +} diff --git a/src/components/ui/Skeleton.js b/src/components/ui/Skeleton.js new file mode 100644 index 000000000..bee3db5ed --- /dev/null +++ b/src/components/ui/Skeleton.js @@ -0,0 +1,12 @@ +import { cn } from "@site/src/lib/utils"; + +function Skeleton({ className, ...props }) { + return ( +
+ ); +} + +export { Skeleton }; diff --git a/src/constants/pricing-tier.js b/src/constants/pricing-tier.js new file mode 100644 index 000000000..f90cc2a08 --- /dev/null +++ b/src/constants/pricing-tier.js @@ -0,0 +1,90 @@ +import { translate } from "@docusaurus/Translate"; + +// export interface Tier { +// name: string; +// id: 'starter' | 'pro' | 'advanced'; +// icon: string; +// description: string; +// features: string[]; +// featured: boolean; +// priceId: Record; +// } + +export const PricingTier = [ + { + name: "Open Source", + id: "opensource", + icon: "🚀", + description: + "Ideal for individuals who want to get started with simple design tasks.", + features: ["1 workspace", "Limited collaboration", "Export to PNG and SVG"], + featured: false, + priceId: "pri_01kecm0y2j5c0e3hak064t62mx", + }, + { + name: "Pro", + id: "pro", + icon: "⭐", + description: + "Enhanced design tools for scaling teams who need more flexibility.", + features: [ + "Integrations", + "Unlimited workspaces", + "Advanced editing tools", + "Everything in Open Source", + ], + featured: true, + priceId: "pri_01kecev08pfk50j8k9p0bs7ag8", + }, +]; + +// export const PricingTier = [ +// { +// name: translate({ id: 'pricing.plan.free', message: 'Free' }), +// icon: '🚀', +// subtitle: translate({ id: 'pricing.plan.free.subtitle', message: 'Community Edition (Open Source)' }), +// price: translate({ id: 'pricing.plan.free.price', message: '$0' }), +// features: [ +// translate({ id: 'pricing.plan.free.unlimited_users', message: 'Unlimited users' }), +// translate({ id: 'pricing.plan.free.unlimited_apps_and_team_members', message: 'Unlimited apps and team members' }), +// translate({ id: 'pricing.plan.free.unlimited_app_version_uploads', message: 'Unlimited app version uploads' }), +// translate({ id: 'pricing.plan.free.unlimited_app_installations', message: 'Unlimited app installations' }), +// translate({ id: 'pricing.plan.free.self_hosted_no_storage_limits', message: 'Self-hosted, no storage limits' }), +// translate({ id: 'pricing.plan.free.community_support', message: 'Community support' }), +// translate({ id: 'pricing.plan.free.open_source_license', message: 'Open source license' }), +// ], +// button: { +// text: translate({ id: 'pricing.plan.free.button', message: 'Get Started' }), +// id: 'github-btn', +// href: 'https://github.com/tryzealot/zealot', +// type: 'link', +// }, +// highlight: false, +// priceId: 'pri_01kecep9wgsx1v52eam2gdnnxy', +// }, +// { +// name: translate({ id: 'pricing.plan.pro', message: 'Pro' }), +// icon: '⭐', +// subtitle: translate({ id: 'pricing.plan.pro.subtitle', message: 'Professional Edition' }), +// price: translate({ id: 'pricing.plan.pro.price', message: '$499 / year' }), +// priceSuffix: '', +// trial: translate({ id: 'pricing.plan.pro.trial', message: 'with a 14 days trial' }), +// save: translate({ id: 'pricing.plan.pro.save', message: 'Save 40%' }), +// features: [ +// translate({ id: 'pricing.plan.pro.all_community_edition_features', message: 'All Community Edition features' }), +// translate({ id: 'pricing.plan.pro.unlimited_organizations', message: 'Unlimited organizations' }), +// translate({ id: 'pricing.plan.pro.detailed_session_logs_and_overviews', message: 'Detailed session logs and overviews' }), +// translate({ id: 'pricing.plan.pro.missing_email_notifications', message: 'Missing email notifications' }), +// translate({ id: 'pricing.plan.pro.more_storage_options', message: 'More storage options' }), +// translate({ id: 'pricing.plan.pro.custom_branding', message: 'Custom branding' }), +// translate({ id: 'pricing.plan.pro.priority_support', message: 'Priority support' }), +// ], +// button: { +// text: translate({ id: 'pricing.plan.pro.button', message: 'Buy Now' }), +// id: 'paddle-buy-btn', +// type: 'paddle', +// }, +// highlight: true, +// priceId: 'pri_01kecep9wgsx1v52eam2gdnnxy', +// }, +// ]; diff --git a/src/constants/regions.js b/src/constants/regions.js new file mode 100644 index 000000000..bba18f59c --- /dev/null +++ b/src/constants/regions.js @@ -0,0 +1,5 @@ +export const Regions = [ + { locale: "en", value: "US", label: "United States" }, + { locale: "zh", value: "CN", label: "China" }, + { locale: "en", value: "OTHERS", label: "Everywhere else" }, +]; diff --git a/src/css/custom.css b/src/css/custom.css index 48de632d2..0e834b9a0 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -4,6 +4,13 @@ * work well for content-centric websites. */ +@layer theme, base, components, utilities; +@import "tailwindcss/theme.css" layer(theme) important; +@import "tailwindcss/utilities.css" layer(utilities) important; +@plugin 'tailwindcss-animate'; + +@custom-variant dark (&:is([data-theme="dark"] *)); + /* You can override the default Infima variables here. */ /* :root { --ifm-color-primary: #2e8555; @@ -29,7 +36,6 @@ --docusaurus-highlighted-code-line-bg: rgba(88, 96, 112, 0.45); } - /* For readability concerns, you should choose a lighter palette in dark mode. */ /* [data-theme='dark'] { --ifm-color-primary: #25c2a0; @@ -42,7 +48,7 @@ --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); } */ -[data-theme='dark'] { +[data-theme="dark"] { --ifm-color-primary: #a3ade7; --ifm-color-primary-dark: #8491df; --ifm-color-primary-darker: #7483db; @@ -56,7 +62,6 @@ margin: 0.3rem 0; } - #announcementBar-2 { background-color: #1fa588; } diff --git a/src/hooks/usePaddlePrices.js b/src/hooks/usePaddlePrices.js new file mode 100644 index 000000000..517ba5df4 --- /dev/null +++ b/src/hooks/usePaddlePrices.js @@ -0,0 +1,34 @@ +import { useEffect, useState } from "react"; +import { PricingTier } from "@site/src/constants/pricing-tier"; + +function getLineItems() { + const priceId = PricingTier.map((tier) => tier.priceId); + return priceId.flat().map((priceId) => ({ priceId, quantity: 1 })); +} + +function getPriceAmounts(prices) { + return prices.data.details.lineItems.reduce((acc, item) => { + acc[item.price.id] = item.formattedTotals.total; + return acc; + }, {}); +} + +export function usePaddlePrices(paddle, country) { + const [prices, setPrices] = useState({}); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const paddlePricePreviewRequest = { + items: getLineItems(), + ...(country !== "OTHERS" && { address: { countryCode: country } }), + }; + + setLoading(true); + + paddle?.PricePreview(paddlePricePreviewRequest).then((prices) => { + setPrices((prevState) => ({ ...prevState, ...getPriceAmounts(prices) })); + setLoading(false); + }); + }, [country, paddle]); + return { prices, loading }; +} diff --git a/src/lib/utils.js b/src/lib/utils.js new file mode 100644 index 000000000..378ccef43 --- /dev/null +++ b/src/lib/utils.js @@ -0,0 +1,6 @@ +import { clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs) { + return twMerge(clsx(inputs)); +} diff --git a/src/pages/pricing.js b/src/pages/pricing.js index 60dea7db5..08fa0de0f 100644 --- a/src/pages/pricing.js +++ b/src/pages/pricing.js @@ -1,74 +1,135 @@ -import Layout from '@theme/Layout'; -import {translate} from '@docusaurus/Translate'; -import styles from './pricing.module.css'; -import clsx from 'clsx'; +import Layout from "@theme/Layout"; +import { translate } from "@docusaurus/Translate"; +import styles from "./pricing.module.css"; +import { Pricing } from "@site/src/components/Pricing"; +import clsx from "clsx"; const plans = [ { - name: translate({ id: 'pricing.plan.free', message: 'Free' }), - icon: '🚀', - subtitle: translate({ id: 'pricing.plan.free.subtitle', message: 'Community Edition (Open Source)' }), - price: translate({ id: 'pricing.plan.free.price', message: '$0' }), + name: translate({ id: "pricing.plan.free", message: "Free" }), + icon: "🚀", + subtitle: translate({ + id: "pricing.plan.free.subtitle", + message: "Community Edition (Open Source)", + }), + price: translate({ id: "pricing.plan.free.price", message: "$0" }), features: [ - translate({ id: 'pricing.plan.free.unlimited_users', message: 'Unlimited users' }), - translate({ id: 'pricing.plan.free.unlimited_apps_and_team_members', message: 'Unlimited apps and team members' }), - translate({ id: 'pricing.plan.free.unlimited_app_version_uploads', message: 'Unlimited app version uploads' }), - translate({ id: 'pricing.plan.free.unlimited_app_installations', message: 'Unlimited app installations' }), - translate({ id: 'pricing.plan.free.self_hosted_no_storage_limits', message: 'Self-hosted, no storage limits' }), - translate({ id: 'pricing.plan.free.community_support', message: 'Community support' }), - translate({ id: 'pricing.plan.free.open_source_license', message: 'Open source license' }), + translate({ + id: "pricing.plan.free.unlimited_users", + message: "Unlimited users", + }), + translate({ + id: "pricing.plan.free.unlimited_apps_and_team_members", + message: "Unlimited apps and team members", + }), + translate({ + id: "pricing.plan.free.unlimited_app_version_uploads", + message: "Unlimited app version uploads", + }), + translate({ + id: "pricing.plan.free.unlimited_app_installations", + message: "Unlimited app installations", + }), + translate({ + id: "pricing.plan.free.self_hosted_no_storage_limits", + message: "Self-hosted, no storage limits", + }), + translate({ + id: "pricing.plan.free.community_support", + message: "Community support", + }), + translate({ + id: "pricing.plan.free.open_source_license", + message: "Open source license", + }), ], button: { - text: translate({ id: 'pricing.plan.free.button', message: 'Get Started' }), - id: 'github-btn', - href: 'https://github.com/tryzealot/zealot', - type: 'link', + text: translate({ + id: "pricing.plan.free.button", + message: "Get Started", + }), + id: "github-btn", + href: "https://github.com/tryzealot/zealot", + type: "link", }, highlight: false, }, { - name: translate({ id: 'pricing.plan.pro', message: 'Pro' }), - icon: '⭐', - subtitle: translate({ id: 'pricing.plan.pro.subtitle', message: 'Professional Edition' }), - price: translate({ id: 'pricing.plan.pro.price', message: '$499 / year' }), - priceSuffix: '', - trial: translate({ id: 'pricing.plan.pro.trial', message: 'with a 14 days trial' }), - save: translate({ id: 'pricing.plan.pro.save', message: 'Save 40%' }), + name: translate({ id: "pricing.plan.pro", message: "Pro" }), + icon: "⭐", + subtitle: translate({ + id: "pricing.plan.pro.subtitle", + message: "Professional Edition", + }), + price: translate({ id: "pricing.plan.pro.price", message: "$499 / year" }), + priceSuffix: "", + trial: translate({ + id: "pricing.plan.pro.trial", + message: "with a 14 days trial", + }), + save: translate({ id: "pricing.plan.pro.save", message: "Save 40%" }), features: [ - translate({ id: 'pricing.plan.pro.all_community_edition_features', message: 'All Community Edition features' }), - translate({ id: 'pricing.plan.pro.unlimited_organizations', message: 'Unlimited organizations' }), - translate({ id: 'pricing.plan.pro.detailed_session_logs_and_overviews', message: 'Detailed session logs and overviews' }), - translate({ id: 'pricing.plan.pro.missing_email_notifications', message: 'Missing email notifications' }), - translate({ id: 'pricing.plan.pro.more_storage_options', message: 'More storage options' }), - translate({ id: 'pricing.plan.pro.custom_branding', message: 'Custom branding' }), - translate({ id: 'pricing.plan.pro.priority_support', message: 'Priority support' }), + translate({ + id: "pricing.plan.pro.all_community_edition_features", + message: "All Community Edition features", + }), + translate({ + id: "pricing.plan.pro.unlimited_organizations", + message: "Unlimited organizations", + }), + translate({ + id: "pricing.plan.pro.detailed_session_logs_and_overviews", + message: "Detailed session logs and overviews", + }), + translate({ + id: "pricing.plan.pro.missing_email_notifications", + message: "Missing email notifications", + }), + translate({ + id: "pricing.plan.pro.more_storage_options", + message: "More storage options", + }), + translate({ + id: "pricing.plan.pro.custom_branding", + message: "Custom branding", + }), + translate({ + id: "pricing.plan.pro.priority_support", + message: "Priority support", + }), ], button: { - text: translate({ id: 'pricing.plan.pro.button', message: 'Buy Now' }), - id: 'paddle-buy-btn', - type: 'paddle', + text: translate({ id: "pricing.plan.pro.button", message: "Buy Now" }), + id: "paddle-buy-btn", + type: "paddle", }, highlight: true, }, ]; const enterprise = { - title: translate({ id: 'pricing.enterprise.title', message: 'Enterprise' }), - description: translate({ id: 'pricing.enterprise.desc', message: 'Need a custom solution or service level agreement? Get in touch for a tailored offer for your company or team.' }), + title: translate({ id: "pricing.enterprise.title", message: "Enterprise" }), + description: translate({ + id: "pricing.enterprise.desc", + message: + "Need a custom solution or service level agreement? Get in touch for a tailored offer for your company or team.", + }), button: { - text: translate({ id: 'pricing.enterprise.button', message: 'Get in touch' }), - href: 'mailto:zealot@ews.im', + text: translate({ + id: "pricing.enterprise.button", + message: "Get in touch", + }), + href: "mailto:zealot@ews.im", }, }; - function PricingCard({ plan }) { return (
{plan.mostPopular && (
Most Popular
@@ -77,12 +138,24 @@ function PricingCard({ plan }) {
{plan.name}
{plan.price} - {plan.priceSuffix && {plan.priceSuffix}} + {plan.priceSuffix && ( + {plan.priceSuffix} + )}
{plan.save &&
{plan.save}
}
{plan.trial}
-
    +
      {plan.features.map((f) => (
    • {f}
    • ))} @@ -94,11 +167,17 @@ function PricingCard({ plan }) { onClick={() => { // Paddle integration placeholder // if type is link, redirect to href - if (plan.button.type === 'link') { + if (plan.button.type === "link") { window.location.href = plan.button.href; return; } - plan.button.id !== 'github-btn' && alert(translate({ id: 'pricing.plan.pro.payment_integration_coming_soon', message: 'Payment integration coming soon!' })); + plan.button.id !== "github-btn" && + alert( + translate({ + id: "pricing.plan.pro.payment_integration_coming_soon", + message: "Payment integration coming soon!", + }) + ); }} > {plan.button.text} @@ -112,7 +191,16 @@ function EnterpriseCard({ enterprise }) {
      🏢
      {enterprise.title}
      -
      {enterprise.description}
      +
      + {enterprise.description} +
      + }} + > + {tier.button.text} + + )} */}
      ); } function EnterpriseCard({ enterprise }) { return ( -
      -
      🏢
      -
      {enterprise.title}
      -
      +
      +
      🏢
      +
      {enterprise.title}
      +
      {enterprise.description}
      +
      + ); +} diff --git a/src/components/Pricing/FeaturesList.js b/src/components/Pricing/FeaturesList.js new file mode 100644 index 000000000..6555fe295 --- /dev/null +++ b/src/components/Pricing/FeaturesList.js @@ -0,0 +1,11 @@ +export function FeaturesList({ features }) { + return ( +
      +
        + {features.map((f) => ( +
      • {f}
      • + ))} +
      +
      + ); +} diff --git a/src/components/Pricing/PriceAmount.js b/src/components/Pricing/PriceAmount.js index 2c0a71303..4d8a0a7cd 100644 --- a/src/components/Pricing/PriceAmount.js +++ b/src/components/Pricing/PriceAmount.js @@ -1,33 +1,47 @@ -import { cn } from "@site/src/lib/utils"; import { Skeleton } from "@site/src/components/ui/Skeleton"; -// interface Props { -// loading: boolean; -// tier: Tier; -// priceMap: Record; -// value: string; -// priceSuffix: string; -// } - -export function PriceAmount({ loading, priceMap, priceSuffix, tier }) { +export function PriceAmount({ loading, paddlePrice, tier }) { return ( -
      - {loading ? ( - - ) : ( - <> -
      - {priceMap[tier.priceId].replace(/\.00$/, "")} -
      -
      - {priceSuffix} -
      - + <> +
      + {loading ? ( + + ) : ( + + {paddlePrice || tier.price}{" "} + {paddlePrice && tier.frequency && ` / ${tier.frequency}`} + + )} +
      + {tier.save && ( +
      + {tier.save} +
      )} -
      +
      + {tier.trial} +
      + ); + + // return ( + //
      + // {loading ? ( + // + // ) : ( + // <> + //
      + // {priceMap[tier.priceId].replace(/\.00$/, "")} + //
      + //
      + // {priceSuffix} + //
      + // + // )} + //
      + // ); } diff --git a/src/components/Pricing/PriceButton.js b/src/components/Pricing/PriceButton.js new file mode 100644 index 000000000..ebde54317 --- /dev/null +++ b/src/components/Pricing/PriceButton.js @@ -0,0 +1,17 @@ +import Link from "@docusaurus/Link"; + +export function PricingButton({ tier }) { + return ( + + {tier.button.text} + + ); +} diff --git a/src/components/Pricing/PriceCards.js b/src/components/Pricing/PriceCards.js index b9a5a9bb6..c7c2b7d69 100644 --- a/src/components/Pricing/PriceCards.js +++ b/src/components/Pricing/PriceCards.js @@ -1,35 +1,60 @@ +import { useMemo } from "react"; +import { + usePaddleClient, + usePaddlePrices, +} from "@site/src/hooks/usePaddlePrices"; import { PricingTier } from "@site/src/constants/pricing-tier"; import { PriceTitle } from "@site/src/components/Pricing/PriceTitle"; import { PriceAmount } from "@site/src/components/Pricing/PriceAmount"; -import { cn } from "@site/src/lib/utils"; +import { FeaturesList } from "@site/src/components/Pricing/FeaturesList"; +import { PricingButton } from "@site/src/components/Pricing/PriceButton"; -export function PriceCards({ loading, priceMap }) { +export function PriceCard({ tier, paddlePrice, loading }) { return ( -
      - {PricingTier.map((tier) => ( -
      -
      - {/* {tier.featured && } */} - - - {/*
      - -
      */} -
      {tier.description}
      -
      -
      - {/* */} -
      - {/* */} -
      - ))} +
      + + + + +
      + ); +} + +export function PriceCards() { + const items = useMemo( + () => + PricingTier.filter((tier) => tier.priceId).map((tier) => ({ + priceId: tier.priceId, + quantity: 1, + })), + [] + ); + + const { paddle } = usePaddleClient(); + const { prices, loading } = usePaddlePrices(paddle, items); + + return ( +
      + {PricingTier.map((tier) => { + const paddlePrice = prices[tier.priceId] + ? prices[tier.priceId].replace(/\.00$/, "") + : null; + return ( + + ); + })}
      ); } diff --git a/src/components/Pricing/PriceTitle.js b/src/components/Pricing/PriceTitle.js index 7341e2f19..c0b33bf39 100644 --- a/src/components/Pricing/PriceTitle.js +++ b/src/components/Pricing/PriceTitle.js @@ -1,10 +1,16 @@ -// import { Tier } from "@site/src/constants/pricing-tier"; import { cn } from "@site/src/lib/utils"; export function PriceTitle({ tier }) { const { name, featured, icon } = tier; return ( -
      + {tier.icon} {tier.name} +
      + ); +} + +{ + /*
      )} -
      - ); +
      */ } diff --git a/src/components/Pricing/index.js b/src/components/Pricing/index.js index d50a34901..f791f1a8a 100644 --- a/src/components/Pricing/index.js +++ b/src/components/Pricing/index.js @@ -26,8 +26,32 @@ export function Pricing({ country }) { }, []); return ( -
      - -
      +
      +
      +
      + {PricingTier.map((tier) => { + const paddlePrice = prices[tier.priceId] + ? prices[tier.priceId].replace(/\.00$/, "") + : null; + return ( +
      + +
      + ); + })} +
      +
      +
      + +
      +
      ); } diff --git a/src/components/ui/Skeleton.js b/src/components/ui/Skeleton.js index bee3db5ed..13dfde1eb 100644 --- a/src/components/ui/Skeleton.js +++ b/src/components/ui/Skeleton.js @@ -3,7 +3,10 @@ import { cn } from "@site/src/lib/utils"; function Skeleton({ className, ...props }) { return (
      ); diff --git a/src/constants/pricing-tier.js b/src/constants/pricing-tier.js index b24e60512..c9ae96de5 100644 --- a/src/constants/pricing-tier.js +++ b/src/constants/pricing-tier.js @@ -1,25 +1,11 @@ import { translate } from "@docusaurus/Translate"; -// export interface Tier { -// name: string; -// id: 'starter' | 'pro' | 'advanced'; -// icon: string; -// description: string; -// features: string[]; -// featured: boolean; -// priceId: Record; -// } - export const PricingTier = [ { - name: translate({ id: "pricing.plan.free", message: "Free" }), + key: "free", + name: translate({ id: "pricing.plan.free", message: "Open Source" }), icon: "🚀", - subtitle: translate({ - id: "pricing.plan.free.subtitle", - message: "Community Edition (Open Source)", - }), - price: translate({ id: "pricing.plan.free.price", message: "$0" }), - priceId: "pri_01kecm0y2j5c0e3hak064t62mx", + price: translate({ id: "pricing.plan.free.price", message: "Free" }), features: [ translate({ id: "pricing.plan.free.unlimited_users", @@ -62,20 +48,19 @@ export const PricingTier = [ highlight: false, }, { + key: "pro", name: translate({ id: "pricing.plan.pro", message: "Pro" }), icon: "⭐", - subtitle: translate({ - id: "pricing.plan.pro.subtitle", - message: "Professional Edition", + priceId: "pri_01kecep9wgsx1v52eam2gdnnxy", + frequency: translate({ + id: "pricing.plan.pro.frequency", + message: "yearly", }), - price: translate({ id: "pricing.plan.pro.price", message: "$499 / year" }), - priceId: "pri_01kecev08pfk50j8k9p0bs7ag8", - priceSuffix: "", trial: translate({ id: "pricing.plan.pro.trial", message: "with a 14 days trial", }), - save: translate({ id: "pricing.plan.pro.save", message: "Save 40%" }), + save: translate({ id: "pricing.plan.pro.save", message: "Save 43%" }), features: [ translate({ id: "pricing.plan.pro.all_community_edition_features", diff --git a/src/constants/regions.js b/src/constants/regions.js index bba18f59c..5b8d45d8a 100644 --- a/src/constants/regions.js +++ b/src/constants/regions.js @@ -1,5 +1,5 @@ export const Regions = [ { locale: "en", value: "US", label: "United States" }, - { locale: "zh", value: "CN", label: "China" }, + { locale: "zh-Hans", value: "CN", label: "China" }, { locale: "en", value: "OTHERS", label: "Everywhere else" }, ]; diff --git a/src/hooks/useGateway.js b/src/hooks/useGateway.js new file mode 100644 index 000000000..7f6a25df8 --- /dev/null +++ b/src/hooks/useGateway.js @@ -0,0 +1,21 @@ +import { useEffect, useState } from "react"; +import { initGateway } from "@site/src/lib/gateway"; + +export function useGateway() { + const [gateway, setGateway] = useState(undefined); + + useEffect(() => { + const baseURL = process.env.ZEALOT_GATEWAY_URL; + + if (baseURL) { + try { + const client = initGateway(baseURL); + setGateway(client); + } catch (error) { + console.error("Failed to initialize Gateway:", error); + } + } + }, []); + + return { gateway }; +} diff --git a/src/hooks/usePaddlePrices.js b/src/hooks/usePaddlePrices.js index 5cd8fea83..74f705a7f 100644 --- a/src/hooks/usePaddlePrices.js +++ b/src/hooks/usePaddlePrices.js @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; import { initializePaddle } from "@paddle/paddle-js"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; function getPriceAmounts(prices) { if (!prices?.data?.details?.lineItems) { @@ -12,35 +13,48 @@ function getPriceAmounts(prices) { }, {}); } -export function usePaddle(options) { +export function usePaddleClient(options) { const [paddle, setPaddle] = useState(undefined); useEffect(() => { - if (process.env.PADDLE_CLIENT_TOKEN && process.env.PADDLE_ENV) { - const a = { - token: process.env.PADDLE_CLIENT_TOKEN, - environment: process.env.PADDLE_ENV, + const token = process.env.PADDLE_CLIENT_TOKEN; + const env = process.env.PADDLE_ENV; + if (token && process.env.PADDLE_ENV) { + const defaults = { + token: token, + environment: env, ...options, }; - console.log("Initializing Paddle with options:", a); - initializePaddle(a).then((paddle) => { + initializePaddle(defaults).then((paddle) => { if (paddle) { setPaddle(paddle); } }); } - }, []); + }, [process.env.PADDLE_CLIENT_TOKEN, process.env.PADDLE_ENV]); return { paddle }; } -export function usePaddlePrices(paddle, country, items) { +export function usePaddlePrices(paddle, items) { const [prices, setPrices] = useState({}); const [loading, setLoading] = useState(false); + const [country, setCountry] = useState("US"); + const { i18n } = useDocusaurusContext(); + + useEffect(() => { + // Map Docusaurus locale to country code for Paddle + const localeToCountry = { + en: "US", + "zh-Hans": "CN", + ja: "JP", + }; + const country = localeToCountry[i18n.currentLocale] || "US"; + setCountry(country); + }, [i18n.currentLocale]); useEffect(() => { - // Don't fetch if paddle is not ready or no items if (!paddle || !items || items.length === 0) { setLoading(false); return; @@ -48,16 +62,14 @@ export function usePaddlePrices(paddle, country, items) { // Build the request payload const paddlePricePreviewRequest = { - items: items, - ...(country && - country !== "OTHERS" && { address: { countryCode: country } }), + items, + country, }; setLoading(true); - // Make the request paddle - ?.PricePreview(paddlePricePreviewRequest) + .PricePreview(paddlePricePreviewRequest) .then((pricesResponse) => { const amounts = getPriceAmounts(pricesResponse); setPrices(amounts); diff --git a/src/lib/gateway.js b/src/lib/gateway.js new file mode 100644 index 000000000..030c3c053 --- /dev/null +++ b/src/lib/gateway.js @@ -0,0 +1,46 @@ +class Gateway { + constructor(baseURL) { + if (!baseURL) { + throw new Error("Gateway baseURL is required"); + } + this.baseURL = baseURL.replace(/\/$/, ""); + } + + async orders(email) { + if (!email) { + throw new Error("Email is required"); + } + + const url = `${this.baseURL}/payments/orders?email=${encodeURIComponent( + email + )}`; + + try { + const response = await fetch(url, { + method: "GET", + }); + + if (!response.ok) { + if (response.status === 404) { + return { orders: [] }; + } + + const errorText = await response.text(); + throw new Error( + `Gateway API request failed: ${response.status} ${response.statusText} - ${errorText}` + ); + } + + return await response.json(); + } catch (error) { + console.error("Failed to fetch orders from gateway:", error); + throw error; + } + } +} + +export function initGateway(baseURL) { + return new Gateway(baseURL); +} + +export default Gateway; diff --git a/src/lib/paddle.js b/src/lib/paddle.js deleted file mode 100644 index 480541c9b..000000000 --- a/src/lib/paddle.js +++ /dev/null @@ -1,14 +0,0 @@ -import { initializePaddle } from "@paddle/paddle-js"; - -export function paddle() { - if (!process.env.PADDLE_CLIENT_TOKEN || !process.env.PADDLE_ENV) { - throw new Error("Paddle client token or environment not set"); - } - - const client = initializePaddle({ - token: process.env.PADDLE_CLIENT_TOKEN, - environment: process.env.PADDLE_ENV, - }); - - return client; -} diff --git a/src/pages/checkout.js b/src/pages/checkout.js deleted file mode 100644 index f36310655..000000000 --- a/src/pages/checkout.js +++ /dev/null @@ -1,48 +0,0 @@ -import { usePaddle } from "@site/src/hooks/usePaddlePrices"; -import { useEffect } from "react"; -import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; -import { Discount } from "@paddle/paddle-node-sdk"; - -// function DisplayColorMode() { -// const { colorMode } = useColorMode(); // ✅ 正常工作 -// return
      当前模式: {colorMode}
      ; -// } - -export default function CheckoutPage() { - const urlParams = new URLSearchParams(window.location.search); - const priceId = urlParams.get("id"); - const { i18n } = useDocusaurusContext(); - - console.log("Language:", i18n.currentLocale); // "en" or "zh-Hans" - // console.log("Theme:", colorMode); // "light" or "dark" - - const checkoutOptions = { - checkout: { - settings: { - variant: "multi-page", - locale: i18n.currentLocale, - allowLogout: false, - showAddTaxId: false, - }, - }, - }; - - const { paddle } = usePaddle(checkoutOptions); - - useEffect(() => { - if (!paddle?.Initialized || !priceId) { - return; - } - - paddle.Checkout.open({ - customer: { email: "user-cn@example.com" }, - discountCode: "JFEIHALLI7", - items: [ - { - priceId: priceId, - quantity: 1, - }, - ], - }); - }, [paddle, priceId]); -} diff --git a/src/pages/checkout/index.js b/src/pages/checkout/index.js new file mode 100644 index 000000000..5941e9846 --- /dev/null +++ b/src/pages/checkout/index.js @@ -0,0 +1,118 @@ +import { useEffect, useMemo } from "react"; +import Layout from "@theme/Layout"; +import BrowserOnly from "@docusaurus/BrowserOnly"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import { useQuery } from "@tanstack/react-query"; +import { useHistory } from "@docusaurus/router"; +import { usePaddleClient } from "@site/src/hooks/usePaddlePrices"; +import { useGateway } from "@site/src/hooks/useGateway"; + +export default function CheckoutPage() { + return ( + + Loading checkout...
      }> + {() => } + + + ); +} +// 客户端渲染的主体组件,避免 SSR 触发 window 报错 +function CheckoutClient() { + const { i18n } = useDocusaurusContext(); + const history = useHistory(); + + const urlParams = useMemo( + () => + typeof window !== "undefined" + ? new URLSearchParams(window.location.search) + : null, + [] + ); + + const priceId = urlParams?.get("id"); + const discountCode = urlParams?.get("code"); + const quantity = urlParams?.get("quantity") || 1; + const email = urlParams?.get("email") || "user-cn@example.com"; + + const basePath = i18n.currentLocale !== "en" ? `/${i18n.currentLocale}` : ""; + + const checkoutOptions = { + checkout: { + settings: { + variant: "multi-page", + locale: i18n.currentLocale, + theme: "dark", + allowLogout: false, + showAddTaxId: false, + successUrl: `${basePath}/checkout/success`, + // displayMode: "inline", + // frameTarget: "checkout-container", + // frameStyle: "min-width: 286px; width: 600px; max-width: 600px;", + // frameInitialHeight: "450", + }, + }, + eventCallback: (data) => { + console.log("Paddle Event:", data); + if (data.name === "checkout.closed") { + history.push(`${basePath}/pricing`); + } + }, + }; + + const { paddle } = usePaddleClient(checkoutOptions); + const { gateway } = useGateway(); + + const getCustomerOrders = async () => { + return gateway.orders(email); + }; + + const query = useQuery({ + queryKey: ["customerOrders"], + queryFn: getCustomerOrders, + enabled: !!email && !!gateway, + }); + + useEffect(() => { + if ( + query.data && + query.data.orders.length === 0 && + paddle?.Initialized && + priceId + ) { + paddle.Checkout.open({ + // customer: { email, address: { countryCode: "CN" } }, + discountCode, + items: [ + { + priceId, + quantity, + }, + ], + }); + + paddle.Spinner.show(); + } + }, [query.data, paddle, priceId, quantity, discountCode]); + + return ( +
      +
      + {(query.isLoading || !query.data) &&
      Loading your orders...
      } + {query.isError &&
      Error loading your orders.
      } + + {query.data && query.data.orders.length > 0 && ( + <> +

      Your Orders

      +
        + {query.data.orders.map((order) => ( +
      • + Order ID: {order.id}, Amount: {order.amount}, Status:{" "} + {order.status} +
      • + ))} +
      + + )} +
      + ); +} diff --git a/src/pages/checkout/success.js b/src/pages/checkout/success.js new file mode 100644 index 000000000..47850ed54 --- /dev/null +++ b/src/pages/checkout/success.js @@ -0,0 +1,12 @@ +import Layout from "@theme/Layout"; + +export default function CheckoutSuccessPage() { + return ( + +
      +

      Checkout Success

      +

      Your payment was successful. You can safely close this page.

      +
      +
      + ); +} diff --git a/src/pages/index.js b/src/pages/index.js index 6607ba1c9..fbe7d94b8 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -1,25 +1,24 @@ -import Layout from '@theme/Layout'; -import {translate} from '@docusaurus/Translate'; -import HomepageHeader from '@site/src/components/HomepageHeader'; -import FeatureSection from '@site/src/components/FeatureSection'; +import Layout from "@theme/Layout"; +import { translate } from "@docusaurus/Translate"; +import HomepageHeader from "@site/src/components/HomepageHeader"; +import FeatureSection from "@site/src/components/FeatureSection"; export default function Home() { return ( + id: "homepage.seo.description", + message: + "Self-hosted Beta App Distribution for Android, iOS, macOS, Windows and Linux apps.", + })} + > -
      - -
      +
      ); } diff --git a/src/pages/pricing.js b/src/pages/pricing.js index abf0a817e..d4caec95b 100644 --- a/src/pages/pricing.js +++ b/src/pages/pricing.js @@ -1,176 +1,25 @@ import Layout from "@theme/Layout"; import { translate } from "@docusaurus/Translate"; -import Link from "@docusaurus/Link"; -import { useEffect, useState, useMemo } from "react"; -import { usePaddle, usePaddlePrices } from "@site/src/hooks/usePaddlePrices"; -import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; -import { PricingTier, EnterpriseTier } from "@site/src/constants/pricing-tier"; - -function PricingCard({ tier, paddlePrice, loading, paddle }) { - return ( -
      - {tier.mostPopular && ( -
      - Most Popular -
      - )} -
      {tier.icon}
      -
      - {tier.name} -
      -
      - {loading ? ( -
      - ) : ( - <> - - {paddlePrice || tier.price} - - {tier.priceSuffix && ( - - {tier.priceSuffix} - - )} - - )} -
      - {tier.save && ( -
      - {tier.save} -
      - )} -
      - {tier.trial} -
      -
      -
        - {tier.features.map((f) => ( -
      • {f}
      • - ))} -
      -
      - - {tier.button.text} - - {/* {tier.button.type === "link" ? ( - - {tier.button.text} - - ) : ( - - - )} */} -
      - ); -} - -function EnterpriseCard({ enterprise }) { - return ( -
      -
      🏢
      -
      {enterprise.title}
      -
      - {enterprise.description} -
      - -
      - ); -} +import { EnterpriseTier } from "@site/src/constants/pricing-tier"; +import { EnterpriseCard } from "@site/src/components/Pricing/EnterpriseCard"; +import { PriceCards } from "@site/src/components/Pricing/PriceCards"; export default function PricingPage() { - const [country, setCountry] = useState("US"); - - // Create items array for Paddle from plans - memoized to prevent infinite requests - const items = useMemo( - () => - PricingTier.filter((tier) => tier.priceId).map((tier) => ({ - priceId: tier.priceId, - quantity: 1, - })), - [] - ); - - const { paddle } = usePaddle(); - const { prices, loading } = usePaddlePrices(paddle, country, items); - const { i18n } = useDocusaurusContext(); - return ( - -
      - {/* Tiers container aligned with enterprise card width */} -
      -
      - {PricingTier.map((tier) => { - const paddlePrice = prices[tier.priceId] - ? prices[tier.priceId].replace(/\.00$/, "") - : null; - return ( -
      - -
      - ); - })} -
      -
      - {/* Enterprise card wrapped in same width container */} -
      - -
      + +
      + +
      ); diff --git a/src/theme/Root/index.js b/src/theme/Root/index.js new file mode 100644 index 000000000..0bee7c6ed --- /dev/null +++ b/src/theme/Root/index.js @@ -0,0 +1,13 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; + +const queryClient = new QueryClient(); + +export default function Root({ children }) { + return ( + + <>{children} + + + ); +} From 1c97ec85def48a39529a6e5ff85671d3d9f9e099 Mon Sep 17 00:00:00 2001 From: icyleaf Date: Mon, 12 Jan 2026 22:15:12 +0800 Subject: [PATCH 04/26] feat: show query devtools in development env only --- src/hooks/useGateway.js | 4 ++++ src/pages/checkout/index.js | 22 +++++++++++----------- src/theme/Root/index.js | 3 ++- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/hooks/useGateway.js b/src/hooks/useGateway.js index 7f6a25df8..bbc4a1ad2 100644 --- a/src/hooks/useGateway.js +++ b/src/hooks/useGateway.js @@ -6,6 +6,10 @@ export function useGateway() { useEffect(() => { const baseURL = process.env.ZEALOT_GATEWAY_URL; + if (!baseURL) { + console.warn("ZEALOT_GATEWAY_URL is not defined"); + return; + } if (baseURL) { try { diff --git a/src/pages/checkout/index.js b/src/pages/checkout/index.js index 5941e9846..ddc533ccb 100644 --- a/src/pages/checkout/index.js +++ b/src/pages/checkout/index.js @@ -7,16 +7,6 @@ import { useHistory } from "@docusaurus/router"; import { usePaddleClient } from "@site/src/hooks/usePaddlePrices"; import { useGateway } from "@site/src/hooks/useGateway"; -export default function CheckoutPage() { - return ( - - Loading checkout...
      }> - {() => } - - - ); -} -// 客户端渲染的主体组件,避免 SSR 触发 window 报错 function CheckoutClient() { const { i18n } = useDocusaurusContext(); const history = useHistory(); @@ -97,7 +87,7 @@ function CheckoutClient() { return (
      - {(query.isLoading || !query.data) &&
      Loading your orders...
      } + {(query.isLoading || !query.data) &&
      Preparing checkout ...
      } {query.isError &&
      Error loading your orders.
      } {query.data && query.data.orders.length > 0 && ( @@ -116,3 +106,13 @@ function CheckoutClient() {
      ); } + +export default function CheckoutPage() { + return ( + + Loading checkout...
      }> + {() => } + + + ); +} diff --git a/src/theme/Root/index.js b/src/theme/Root/index.js index 0bee7c6ed..5bf41f190 100644 --- a/src/theme/Root/index.js +++ b/src/theme/Root/index.js @@ -4,10 +4,11 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; const queryClient = new QueryClient(); export default function Root({ children }) { + const env = process.env.ENVIRONMENT || "production"; return ( <>{children} - + {env === "development" && } ); } From 9cfeb7ec558c351dde2440f8df7f14842d0302ca Mon Sep 17 00:00:00 2001 From: icyleaf Date: Sat, 7 Mar 2026 12:03:58 +0800 Subject: [PATCH 05/26] chore: upgrade react query version to 5.90.21 --- package.json | 4 +- pnpm-lock.yaml | 108 +++++++++++++++++++++++++++++++++---------------- 2 files changed, 75 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index b15ea92b7..326928fc3 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "@gracefullight/docusaurus-plugin-microsoft-clarity": "^1.0.0", "@mdx-js/react": "^3.1.1", "@paddle/paddle-js": "^1.6.1", - "@tanstack/react-query": "^5.90.16", - "@tanstack/react-query-devtools": "^5.91.2", + "@tanstack/react-query": "^5.90.21", + "@tanstack/react-query-devtools": "^5.91.3", "clsx": "^2.1.1", "docusaurus2-dotenv": "^1.4.0", "prism-react-renderer": "^2.4.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57b4534e3..fab53a725 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,11 +27,11 @@ importers: specifier: ^1.6.1 version: 1.6.1 '@tanstack/react-query': - specifier: ^5.90.16 - version: 5.90.16(react@19.0.0) + specifier: ^5.90.21 + version: 5.90.21(react@19.0.0) '@tanstack/react-query-devtools': - specifier: ^5.91.2 - version: 5.91.2(@tanstack/react-query@5.90.16(react@19.0.0))(react@19.0.0) + specifier: ^5.91.3 + version: 5.91.3(@tanstack/react-query@5.90.21(react@19.0.0))(react@19.0.0) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -1251,8 +1251,8 @@ packages: resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.3.3': - resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + '@eslint/eslintrc@3.3.4': + resolution: {integrity: sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@9.39.2': @@ -1571,24 +1571,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -1626,20 +1630,20 @@ packages: peerDependencies: eslint: ^8.57.0 || ^9.0.0 - '@tanstack/query-core@5.90.16': - resolution: {integrity: sha512-MvtWckSVufs/ja463/K4PyJeqT+HMlJWtw6PrCpywznd2NSgO3m4KwO9RqbFqGg6iDE8vVMFWMeQI4Io3eEYww==} + '@tanstack/query-core@5.90.20': + resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} - '@tanstack/query-devtools@5.92.0': - resolution: {integrity: sha512-N8D27KH1vEpVacvZgJL27xC6yPFUy0Zkezn5gnB3L3gRCxlDeSuiya7fKge8Y91uMTnC8aSxBQhcK6ocY7alpQ==} + '@tanstack/query-devtools@5.93.0': + resolution: {integrity: sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg==} - '@tanstack/react-query-devtools@5.91.2': - resolution: {integrity: sha512-ZJ1503ay5fFeEYFUdo7LMNFzZryi6B0Cacrgr2h1JRkvikK1khgIq6Nq2EcblqEdIlgB/r7XDW8f8DQ89RuUgg==} + '@tanstack/react-query-devtools@5.91.3': + resolution: {integrity: sha512-nlahjMtd/J1h7IzOOfqeyDh5LNfG0eULwlltPEonYy0QL+nqrBB+nyzJfULV+moL7sZyxc2sHdNJki+vLA9BSA==} peerDependencies: - '@tanstack/react-query': ^5.90.14 + '@tanstack/react-query': ^5.90.20 react: ^18 || ^19 - '@tanstack/react-query@5.90.16': - resolution: {integrity: sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==} + '@tanstack/react-query@5.90.21': + resolution: {integrity: sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==} peerDependencies: react: ^18 || ^19 @@ -1674,6 +1678,9 @@ packages: '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/express-serve-static-core@4.19.6': resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} @@ -1947,6 +1954,9 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} @@ -2080,6 +2090,9 @@ packages: brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -3007,8 +3020,8 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + flatted@3.3.4: + resolution: {integrity: sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==} follow-redirects@1.15.9: resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} @@ -3638,24 +3651,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -4021,6 +4038,9 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@5.1.6: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} @@ -7617,7 +7637,7 @@ snapshots: '@docusaurus/react-loadable@6.0.0(react@19.0.0)': dependencies: - '@types/react': 19.2.9 + '@types/react': 19.1.12 react: 19.0.0 '@docusaurus/theme-classic@3.9.2(@types/react@19.2.9)(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': @@ -7937,7 +7957,7 @@ snapshots: dependencies: '@eslint/object-schema': 2.1.7 debug: 4.4.3 - minimatch: 3.1.2 + minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -7949,16 +7969,16 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.3': + '@eslint/eslintrc@3.3.4': dependencies: - ajv: 6.12.6 + ajv: 6.14.0 debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 js-yaml: 4.1.1 - minimatch: 3.1.2 + minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color @@ -8381,19 +8401,19 @@ snapshots: - supports-color - typescript - '@tanstack/query-core@5.90.16': {} + '@tanstack/query-core@5.90.20': {} - '@tanstack/query-devtools@5.92.0': {} + '@tanstack/query-devtools@5.93.0': {} - '@tanstack/react-query-devtools@5.91.2(@tanstack/react-query@5.90.16(react@19.0.0))(react@19.0.0)': + '@tanstack/react-query-devtools@5.91.3(@tanstack/react-query@5.90.21(react@19.0.0))(react@19.0.0)': dependencies: - '@tanstack/query-devtools': 5.92.0 - '@tanstack/react-query': 5.90.16(react@19.0.0) + '@tanstack/query-devtools': 5.93.0 + '@tanstack/react-query': 5.90.21(react@19.0.0) react: 19.0.0 - '@tanstack/react-query@5.90.16(react@19.0.0)': + '@tanstack/react-query@5.90.21(react@19.0.0)': dependencies: - '@tanstack/query-core': 5.90.16 + '@tanstack/query-core': 5.90.20 react: 19.0.0 '@trysound/sax@0.2.0': {} @@ -8436,6 +8456,8 @@ snapshots: '@types/estree@1.0.7': {} + '@types/estree@1.0.8': {} + '@types/express-serve-static-core@4.19.6': dependencies: '@types/node': 22.15.18 @@ -8765,6 +8787,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 @@ -8939,6 +8968,11 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 @@ -9680,14 +9714,14 @@ snapshots: '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.3 + '@eslint/eslintrc': 3.3.4 '@eslint/js': 9.39.2 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.7 - ajv: 6.12.6 + '@types/estree': 1.0.8 + ajv: 6.14.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 @@ -9706,7 +9740,7 @@ snapshots: is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 lodash.merge: 4.6.2 - minimatch: 3.1.2 + minimatch: 3.1.5 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: @@ -9931,12 +9965,12 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.3.3 + flatted: 3.3.4 keyv: 4.5.4 flat@5.0.2: {} - flatted@3.3.3: {} + flatted@3.3.4: {} follow-redirects@1.15.9: {} @@ -11222,6 +11256,10 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.12 + minimatch@5.1.6: dependencies: brace-expansion: 2.0.1 From 2b29a3a5540b31e16329576c0db2633ff6a2a6d8 Mon Sep 17 00:00:00 2001 From: icyleaf Date: Sat, 7 Mar 2026 12:04:26 +0800 Subject: [PATCH 06/26] feat(style): show skeleton during fetch paddle price --- src/components/Pricing/PriceAmount.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Pricing/PriceAmount.js b/src/components/Pricing/PriceAmount.js index 4d8a0a7cd..e40d75b00 100644 --- a/src/components/Pricing/PriceAmount.js +++ b/src/components/Pricing/PriceAmount.js @@ -1,11 +1,12 @@ import { Skeleton } from "@site/src/components/ui/Skeleton"; export function PriceAmount({ loading, paddlePrice, tier }) { + console.log("PriceAmount", { loading, paddlePrice, tier }); return ( <>
      {loading ? ( - + ) : ( {paddlePrice || tier.price}{" "} From 39145359b0277337f1ba7b08b900def888a4df43 Mon Sep 17 00:00:00 2001 From: icyleaf Date: Sat, 7 Mar 2026 22:07:37 +0800 Subject: [PATCH 07/26] feat: add paddle payment success --- src/components/Pricing/PriceAmount.js | 1 - src/hooks/usePaddlePrices.js | 10 +- src/lib/gateway.js | 30 +++++ src/lib/utils.js | 30 +++++ src/pages/checkout/failed.js | 78 ++++++++++++ src/pages/checkout/index.js | 132 ++++++++++++++++---- src/pages/checkout/success.js | 172 +++++++++++++++++++++++++- 7 files changed, 414 insertions(+), 39 deletions(-) create mode 100644 src/pages/checkout/failed.js diff --git a/src/components/Pricing/PriceAmount.js b/src/components/Pricing/PriceAmount.js index e40d75b00..32ac91e00 100644 --- a/src/components/Pricing/PriceAmount.js +++ b/src/components/Pricing/PriceAmount.js @@ -1,7 +1,6 @@ import { Skeleton } from "@site/src/components/ui/Skeleton"; export function PriceAmount({ loading, paddlePrice, tier }) { - console.log("PriceAmount", { loading, paddlePrice, tier }); return ( <>
      diff --git a/src/hooks/usePaddlePrices.js b/src/hooks/usePaddlePrices.js index 74f705a7f..174441012 100644 --- a/src/hooks/usePaddlePrices.js +++ b/src/hooks/usePaddlePrices.js @@ -1,6 +1,7 @@ import { useEffect, useState } from "react"; import { initializePaddle } from "@paddle/paddle-js"; import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import { getCountryCodeFromLocale } from "@site/src/lib/utils"; function getPriceAmounts(prices) { if (!prices?.data?.details?.lineItems) { @@ -44,14 +45,7 @@ export function usePaddlePrices(paddle, items) { const { i18n } = useDocusaurusContext(); useEffect(() => { - // Map Docusaurus locale to country code for Paddle - const localeToCountry = { - en: "US", - "zh-Hans": "CN", - ja: "JP", - }; - const country = localeToCountry[i18n.currentLocale] || "US"; - setCountry(country); + setCountry(getCountryCodeFromLocale(i18n.currentLocale)); }, [i18n.currentLocale]); useEffect(() => { diff --git a/src/lib/gateway.js b/src/lib/gateway.js index 030c3c053..5feea1921 100644 --- a/src/lib/gateway.js +++ b/src/lib/gateway.js @@ -37,6 +37,36 @@ class Gateway { throw error; } } + + async generateLicense(data) { + if (!data) { + throw new Error("License data is required"); + } + + const url = `${this.baseURL}/payments/license`; + + try { + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error( + `Failed to generate license: ${response.status} ${response.statusText} - ${errorText}` + ); + } + + return await response.json(); + } catch (error) { + console.error("Failed to generate license:", error); + throw error; + } + } } export function initGateway(baseURL) { diff --git a/src/lib/utils.js b/src/lib/utils.js index 378ccef43..72fea6ea9 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -4,3 +4,33 @@ import { twMerge } from "tailwind-merge"; export function cn(...inputs) { return twMerge(clsx(inputs)); } + +/** + * Map Docusaurus locale to country code for payment services + * @param {string} locale - The Docusaurus locale (e.g., 'en', 'zh-Hans', 'ja') + * @returns {string} - The country code (e.g., 'US', 'CN', 'JP') + */ +export function getCountryCodeFromLocale(locale) { + const localeToCountryMap = { + en: "US", + "zh-Hans": "CN", + "zh-Hant": "TW", + ja: "JP", + ko: "KR", + de: "DE", + fr: "FR", + es: "ES", + ru: "RU", + it: "IT", + pt: "PT", + nl: "NL", + pl: "PL", + tr: "TR", + ar: "SA", + th: "TH", + vi: "VN", + id: "ID", + ms: "MY", + }; + return localeToCountryMap[locale] || "US"; // Default to US +} diff --git a/src/pages/checkout/failed.js b/src/pages/checkout/failed.js new file mode 100644 index 000000000..56d41db9e --- /dev/null +++ b/src/pages/checkout/failed.js @@ -0,0 +1,78 @@ +import Layout from "@theme/Layout"; +import BrowserOnly from "@docusaurus/BrowserOnly"; +import { useEffect, useState } from "react"; +import { useHistory } from "@docusaurus/router"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; + +function FailedClient() { + const history = useHistory(); + const { i18n } = useDocusaurusContext(); + const [errorInfo, setErrorInfo] = useState(null); + + useEffect(() => { + // 从 sessionStorage 读取错误信息 + const storedData = sessionStorage.getItem("paddleError"); + if (storedData) { + const errorData = JSON.parse(storedData); + setErrorInfo(errorData); + console.log("Payment failed:", errorData); + // 清除 sessionStorage 中的数据 + sessionStorage.removeItem("paddleError"); + } else { + setErrorInfo({ errorCode: "unknown", errorMessage: "Payment failed" }); + } + }, []); + + const basePath = i18n.currentLocale !== "en" ? `/${i18n.currentLocale}` : ""; + + return ( +
      +
      +

      Payment Failed

      +

      + We're sorry, but your payment could not be processed. +

      + + {errorInfo?.errorMessage && ( +
      +

      + Error: {errorInfo.errorMessage} +

      +
      + )} + +
      + + +
      + +
      +

      Need Help?

      +

      + If you continue to experience issues, please contact our support team + with details about your purchase attempt. +

      +
      +
      + ); +} + +export default function CheckoutFailedPage() { + return ( + + Loading...
      }> + {() => } + + + ); +} \ No newline at end of file diff --git a/src/pages/checkout/index.js b/src/pages/checkout/index.js index ddc533ccb..4290e9ced 100644 --- a/src/pages/checkout/index.js +++ b/src/pages/checkout/index.js @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import Layout from "@theme/Layout"; import BrowserOnly from "@docusaurus/BrowserOnly"; import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; @@ -6,6 +6,7 @@ import { useQuery } from "@tanstack/react-query"; import { useHistory } from "@docusaurus/router"; import { usePaddleClient } from "@site/src/hooks/usePaddlePrices"; import { useGateway } from "@site/src/hooks/useGateway"; +import { getCountryCodeFromLocale } from "@site/src/lib/utils"; function CheckoutClient() { const { i18n } = useDocusaurusContext(); @@ -22,11 +23,15 @@ function CheckoutClient() { const priceId = urlParams?.get("id"); const discountCode = urlParams?.get("code"); const quantity = urlParams?.get("quantity") || 1; - const email = urlParams?.get("email") || "user-cn@example.com"; + + const [userEmail, setUserEmail] = useState(""); + const [submittedEmail, setSubmittedEmail] = useState(""); + const [hasSubmitted, setHasSubmitted] = useState(false); const basePath = i18n.currentLocale !== "en" ? `/${i18n.currentLocale}` : ""; + const countryCode = getCountryCodeFromLocale(i18n.currentLocale); - const checkoutOptions = { +const checkoutOptions = { checkout: { settings: { variant: "multi-page", @@ -42,9 +47,48 @@ function CheckoutClient() { }, }, eventCallback: (data) => { - console.log("Paddle Event:", data); - if (data.name === "checkout.closed") { - history.push(`${basePath}/pricing`); + switch (data.name) { + case "checkout.completed": + // 支付成功完成 + console.log("Payment completed:", data); + // 将交易数据存储到 sessionStorage + const transactionData = { + transactionId: data.data.transaction_id, + checkoutId: data.data.id, + customerEmail: data.data.customer?.email, + priceId: data.data.items?.[0]?.price_id, + amount: data.data.totals?.total, + }; + sessionStorage.setItem("paddleTransaction", JSON.stringify(transactionData)); + // 手动跳转到 success 页面 + history.push(`${basePath}/checkout/success`); + break; + + case "checkout.payment.failed": + // 支付失败 + console.error("Payment failed:", data); + // 存储错误信息并跳转到失败页面 + const errorData = { + errorCode: data.data?.error?.code, + errorMessage: data.data?.error?.message, + }; + sessionStorage.setItem("paddleError", JSON.stringify(errorData)); + history.push(`${basePath}/checkout/failed`); + break; + + case "checkout.closed": + // 用户关闭了 checkout(可能是取消支付) + console.log("Checkout closed"); + history.push(`${basePath}/pricing`); + break; + + case "checkout.customer.updated": + // 客户信息更新 + console.log("Customer updated:", data); + break; + + default: + console.log("Unknown event:", data.name); } }, }; @@ -53,15 +97,23 @@ function CheckoutClient() { const { gateway } = useGateway(); const getCustomerOrders = async () => { - return gateway.orders(email); + return gateway.orders(submittedEmail); }; const query = useQuery({ - queryKey: ["customerOrders"], + queryKey: ["customerOrders", submittedEmail], queryFn: getCustomerOrders, - enabled: !!email && !!gateway, + enabled: !!submittedEmail && !!gateway && hasSubmitted, }); + const handleEmailSubmit = (e) => { + e.preventDefault(); + if (userEmail.trim()) { + setSubmittedEmail(userEmail.trim()); + setHasSubmitted(true); + } + }; + useEffect(() => { if ( query.data && @@ -69,8 +121,8 @@ function CheckoutClient() { paddle?.Initialized && priceId ) { - paddle.Checkout.open({ - // customer: { email, address: { countryCode: "CN" } }, + const options = { + customer: { email: submittedEmail, address: { countryCode } }, discountCode, items: [ { @@ -78,29 +130,57 @@ function CheckoutClient() { quantity, }, ], - }); - + customData: { + customerEmail: submittedEmail, + priceId: priceId, + }, + }; + console.log("Opening Paddle checkout with options:", options); + paddle.Checkout.open(options); paddle.Spinner.show(); } - }, [query.data, paddle, priceId, quantity, discountCode]); + }, [query.data, paddle, priceId, quantity, discountCode, submittedEmail, countryCode]); return (
      - {(query.isLoading || !query.data) &&
      Preparing checkout ...
      } - {query.isError &&
      Error loading your orders.
      } - {query.data && query.data.orders.length > 0 && ( + {!hasSubmitted ? ( +
      +

      Enter your email to continue

      + setUserEmail(e.target.value)} + placeholder="your@email.com" + className="px-4 py-2 border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-800 text-gray-900 dark:text-white" + required + /> + +
      + ) : ( <> -

      Your Orders

      -
        - {query.data.orders.map((order) => ( -
      • - Order ID: {order.id}, Amount: {order.amount}, Status:{" "} - {order.status} -
      • - ))} -
      + {(query.isLoading || !query.data) &&
      Preparing checkout ...
      } + {query.isError &&
      Error loading your orders.
      } + + {query.data && query.data.orders.length > 0 && ( + <> +

      Your Orders

      +
        + {query.data.orders.map((order) => ( +
      • + Order ID: {order.id}, Amount: {order.amount}, Status:{" "} + {order.status} +
      • + ))} +
      + + )} )}
      diff --git a/src/pages/checkout/success.js b/src/pages/checkout/success.js index 47850ed54..f566e29da 100644 --- a/src/pages/checkout/success.js +++ b/src/pages/checkout/success.js @@ -1,12 +1,176 @@ import Layout from "@theme/Layout"; +import BrowserOnly from "@docusaurus/BrowserOnly"; +import { useEffect, useState } from "react"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import { useGateway } from "@site/src/hooks/useGateway"; + +function SuccessClient() { + const { gateway } = useGateway(); + const [isLoading, setIsLoading] = useState(true); + const [license, setLicense] = useState(null); + const [error, setError] = useState(null); + const [transactionInfo, setTransactionInfo] = useState(null); + const [copied, setCopied] = useState(false); + + const copyToClipboard = async (text) => { + try { + await navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error("Failed to copy:", err); + } + }; + + useEffect(() => { + // 从 sessionStorage 读取交易数据 + const storedData = sessionStorage.getItem("paddleTransaction"); + if (storedData) { + const transactionData = JSON.parse(storedData); + setTransactionInfo(transactionData); + // 清除 sessionStorage 中的数据 + // sessionStorage.removeItem("paddleTransaction"); + } else { + setError("No transaction information found"); + setIsLoading(false); + } + }, []); + + // 当 gateway 初始化完成且有交易数据时,生成 license + useEffect(() => { + if (gateway && transactionInfo && !license && !error && isLoading) { + const data = { + transactionId: transactionInfo.transactionId, + checkoutId: transactionInfo.checkoutId, + customerEmail: transactionInfo.customerEmail, + priceId: transactionInfo.priceId, + }; + + console.log("Generating license with data:", data); + gateway + .generateLicense(data) + .then((result) => { + console.log("License API response:", result); + if (result.success) { + setLicense(result); + setIsLoading(false); + } else { + setError(result.message || "Failed to generate license"); + setIsLoading(false); + } + }) + .catch((err) => { + console.error("Failed to generate license:", err); + setError(err.message || "Failed to generate license"); + setIsLoading(false); + }); + } + }, [gateway, transactionInfo, license, error, isLoading]); + + return ( +
      + {isLoading ? ( + <> +
      +

      Processing your payment...

      +

      + Please wait while we generate your license. +

      + + ) : error ? ( + <> +
      ⚠️
      +

      License Generation Failed

      +

      {error}

      +

      + Your payment was successful, but we encountered an issue generating your license. + Please contact support with your transaction ID. +

      + {transactionInfo?.transactionId && ( +
      +

      Transaction ID

      + {transactionInfo.transactionId} +
      + )} + + + ) : ( + <> +
      +

      Payment Successful!

      +

      + Thank you for your purchase. Your order has been processed successfully. +

      + + {license && ( +
      +

      Your License

      + +
      +

      License Key

      + {license.license?.key} + +
      + +
      +
      + Order ID: + {license.purchase?.id} +
      +
      + Email: + {license.customer?.email} +
      +
      + Plan: + {license.purchase?.planType} +
      +
      + Type: + {license.purchase?.productType} +
      +
      + Expires: + + {new Date(license.license?.expiredAt).toLocaleDateString()} + +
      +
      + +

      + Please save your license key in a safe place. +

      +
      + )} + + + + )} +
      + ); +} export default function CheckoutSuccessPage() { return ( -
      -

      Checkout Success

      -

      Your payment was successful. You can safely close this page.

      -
      + Loading...
      }> + {() => } + ); } From f8233a66848aa4022ed2521941d5518901171026 Mon Sep 17 00:00:00 2001 From: icyleaf Date: Tue, 10 Mar 2026 12:32:52 +0800 Subject: [PATCH 08/26] feat: add orders list page --- src/lib/crypto.js | 114 +++++++++++++++ src/lib/query.js | 32 +++++ src/pages/checkout/index.js | 81 +++++++---- src/pages/orders/index.js | 280 ++++++++++++++++++++++++++++++++++++ 4 files changed, 478 insertions(+), 29 deletions(-) create mode 100644 src/lib/crypto.js create mode 100644 src/lib/query.js create mode 100644 src/pages/orders/index.js diff --git a/src/lib/crypto.js b/src/lib/crypto.js new file mode 100644 index 000000000..6d5cf79a0 --- /dev/null +++ b/src/lib/crypto.js @@ -0,0 +1,114 @@ +/** + * Encryption utility functions - using AES-GCM algorithm + */ + +const salt = process.env.ZEALOT_ENCRYPTION_SALT; +if (!salt) { + throw new Error("ZEALOT_ENCRYPTION_SALT is not defined"); +} + +/** + * Generate encryption key from string + * @param {string} keyString - Key string + * @returns {Promise} + */ +async function generateKey(keyString) { + const encoder = new TextEncoder(); + const keyData = encoder.encode(keyString); + + // Derive 256-bit key from key string using PBKDF2 + const keyMaterial = await crypto.subtle.importKey( + 'raw', + keyData, + 'PBKDF2', + false, + ['deriveKey'] + ); + + return crypto.subtle.deriveKey( + { + name: 'PBKDF2', + salt: encoder.encode(salt), + iterations: 100000, + hash: 'SHA-256', + }, + keyMaterial, + { name: 'AES-GCM', length: 256 }, + false, + ['encrypt', 'decrypt'] + ); +} + +/** + * Encrypt text + * @param {string} text - Text to encrypt + * @param {string} secretKey - Encryption key (32 characters) + * @returns {Promise} - Returns format: base64(iv):base64(encrypted data) + */ +export async function encrypt(text, secretKey) { + const encoder = new TextEncoder(); + const data = encoder.encode(text); + + // Generate random IV (12 bytes) + const iv = crypto.getRandomValues(new Uint8Array(12)); + + // Generate key + const key = await generateKey(secretKey); + + // Encrypt + const encrypted = await crypto.subtle.encrypt( + { + name: 'AES-GCM', + iv: iv, + }, + key, + data + ); + + // Convert to base64 + const ivBase64 = btoa(String.fromCharCode(...iv)); + const encryptedBase64 = btoa(String.fromCharCode(...new Uint8Array(encrypted))); + + return `${ivBase64}:${encryptedBase64}`; +} + +/** + * Decrypt text + * @param {string} encryptedData - Encrypted data (format: base64(iv):base64(encrypted data)) + * @param {string} secretKey - Encryption key (32 characters) + * @returns {Promise} - Decrypted text + */ +export async function decrypt(encryptedData, secretKey) { + const [ivBase64, encryptedBase64] = encryptedData.split(':'); + + // Parse IV + const iv = new Uint8Array( + atob(ivBase64) + .split('') + .map((c) => c.charCodeAt(0)) + ); + + // Parse encrypted data + const encrypted = new Uint8Array( + atob(encryptedBase64) + .split('') + .map((c) => c.charCodeAt(0)) + ); + + // Generate key + const key = await generateKey(secretKey); + + // Decrypt + const decrypted = await crypto.subtle.decrypt( + { + name: 'AES-GCM', + iv: iv, + }, + key, + encrypted + ); + + // Convert to string + const decoder = new TextDecoder(); + return decoder.decode(decrypted); +} \ No newline at end of file diff --git a/src/lib/query.js b/src/lib/query.js new file mode 100644 index 000000000..579a2928b --- /dev/null +++ b/src/lib/query.js @@ -0,0 +1,32 @@ +import { useQuery } from "@tanstack/react-query"; +import { encrypt } from "./crypto"; + +const secretKey = process.env.ZEALOT_ENCRYPTION_KEY; +if (!secretKey) { + throw new Error("ZEALOT_ENCRYPTION_KEY is not defined"); +} + +/** + * Get customer orders + * @param {Object} gateway - Gateway instance + * @param {string} email - Customer email + * @param {boolean} enabled - Whether to enable query + * @returns {Object} React Query result + */ +export const useCustomerOrders = (gateway, email, enabled = false) => { + const getCustomerOrders = async () => { + // Get encryption key from environment variable + + + // Encrypt email + const encryptedEmail = await encrypt(email, secretKey); + + return gateway.orders(encryptedEmail); + }; + + return useQuery({ + queryKey: ["customerOrders", email], + queryFn: getCustomerOrders, + enabled: !!email && !!gateway && enabled, + }); +}; \ No newline at end of file diff --git a/src/pages/checkout/index.js b/src/pages/checkout/index.js index 4290e9ced..90247314f 100644 --- a/src/pages/checkout/index.js +++ b/src/pages/checkout/index.js @@ -2,11 +2,11 @@ import { useEffect, useMemo, useState } from "react"; import Layout from "@theme/Layout"; import BrowserOnly from "@docusaurus/BrowserOnly"; import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; -import { useQuery } from "@tanstack/react-query"; import { useHistory } from "@docusaurus/router"; import { usePaddleClient } from "@site/src/hooks/usePaddlePrices"; import { useGateway } from "@site/src/hooks/useGateway"; import { getCountryCodeFromLocale } from "@site/src/lib/utils"; +import { useCustomerOrders } from "@site/src/lib/query"; function CheckoutClient() { const { i18n } = useDocusaurusContext(); @@ -96,15 +96,7 @@ const checkoutOptions = { const { paddle } = usePaddleClient(checkoutOptions); const { gateway } = useGateway(); - const getCustomerOrders = async () => { - return gateway.orders(submittedEmail); - }; - - const query = useQuery({ - queryKey: ["customerOrders", submittedEmail], - queryFn: getCustomerOrders, - enabled: !!submittedEmail && !!gateway && hasSubmitted, - }); + const query = useCustomerOrders(gateway, submittedEmail, hasSubmitted); const handleEmailSubmit = (e) => { e.preventDefault(); @@ -121,8 +113,9 @@ const checkoutOptions = { paddle?.Initialized && priceId ) { + const name = submittedEmail.split('@')[0]; const options = { - customer: { email: submittedEmail, address: { countryCode } }, + customer: { email: submittedEmail, name, address: { countryCode } }, discountCode, items: [ { @@ -146,40 +139,70 @@ const checkoutOptions = {
      {!hasSubmitted ? ( -
      +

      Enter your email to continue

      setUserEmail(e.target.value)} placeholder="your@email.com" - className="px-4 py-2 border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-800 text-gray-900 dark:text-white" + className="w-full px-5 py-3 text-lg border-2 border-gray-300 rounded-lg focus:outline-none focus:border-[--ifm-color-primary] dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:focus:border-[--ifm-color-primary] transition-colors" required />
      ) : ( <> - {(query.isLoading || !query.data) &&
      Preparing checkout ...
      } - {query.isError &&
      Error loading your orders.
      } - - {query.data && query.data.orders.length > 0 && ( - <> -

      Your Orders

      -
        - {query.data.orders.map((order) => ( -
      • - Order ID: {order.id}, Amount: {order.amount}, Status:{" "} - {order.status} -
      • - ))} -
      - + {query.isError ? ( +
      +

      Error loading your orders.

      +
      + ) : (query.isLoading || !query.data) && ( +
      +
      +

      Preparing checkout...

      +
      + )} + + {query.data && query.data.orders?.length > 0 && ( +
      +
      +
      + + + +

      Existing Orders Found

      +
      +

      + You have {query.data.orders.length} order(s) associated with this email. View your complete order history, license details, and subscription information. +

      +
      + + +
      +
      +
      )} )} diff --git a/src/pages/orders/index.js b/src/pages/orders/index.js new file mode 100644 index 000000000..acbd9bd7e --- /dev/null +++ b/src/pages/orders/index.js @@ -0,0 +1,280 @@ +import { useState } from "react"; +import Layout from "@theme/Layout"; +import BrowserOnly from "@docusaurus/BrowserOnly"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import { useHistory } from "@docusaurus/router"; +import { useGateway } from "@site/src/hooks/useGateway"; +import { useCustomerOrders } from "@site/src/lib/query"; + +function OrdersClient() { + const { i18n } = useDocusaurusContext(); + const history = useHistory(); + + const [userEmail, setUserEmail] = useState(""); + const [submittedEmail, setSubmittedEmail] = useState(""); + const [hasSubmitted, setHasSubmitted] = useState(false); + + const basePath = i18n.currentLocale !== "en" ? `/${i18n.currentLocale}` : ""; + + const { gateway } = useGateway(); + const query = useCustomerOrders(gateway, submittedEmail, hasSubmitted); + + const handleEmailSubmit = (e) => { + e.preventDefault(); + if (userEmail.trim()) { + setSubmittedEmail(userEmail.trim()); + setHasSubmitted(true); + } + }; + + const handleBack = () => { + history.push(`${basePath}/pricing`); + }; + + return ( +
      + {!hasSubmitted ? ( +
      +

      Enter your email to view orders

      + setUserEmail(e.target.value)} + placeholder="your@email.com" + className="w-full px-5 py-3 text-lg border-2 border-gray-300 rounded-lg focus:outline-none focus:border-[--ifm-color-primary] dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:focus:border-[--ifm-color-primary] transition-colors" + required + /> + + +
      + ) : ( + <> + {query.isError ? ( +
      +
      +

      Error loading your orders.

      +
      + +
      + ) : (query.isLoading || !query.data) && ( +
      +
      +

      Loading your orders...

      +
      + )} + + {query.data && ( +
      +
      +

      Your Orders

      + {submittedEmail} +
      + + {query.data.orders?.length === 0 ? ( +
      +
      +

      No orders found for this email.

      +
      +
      + + +
      +
      + ) : ( + <> +
      + {/* Customer Info */} + {query.data.customer && ( +
      +

      Customer

      +
      + Email + {query.data.customer.email} +
      +
      + Customer ID + {query.data.customer.id} +
      +
      + )} + + {/* License Info */} + {query.data.license && ( +
      +

      License

      +
      + License Key + {query.data.license.key} +
      +
      + Status + + {query.data.license.active ? 'Active' : 'Inactive'} + +
      +
      + )} + + {/* Orders */} + {query.data.orders.map((order) => ( +
      +

      Order

      +
      + + Order ID + + + {order.id} + +
      +
      + + Amount + + + {(order.totalAmount).toFixed(2)} {order.currency} + +
      +
      + + Created + + + {new Date(order.createdAt).toLocaleDateString()} + +
      +
      + + Paid At + + + {order.paidAt ? new Date(order.paidAt).toLocaleDateString() : '-'} + +
      +
      + + Status + + + {order.status.charAt(0).toUpperCase() + order.status.slice(1)} + +
      +
      + ))} + + + {/* Subscription Info */} + {query.data.subscription && ( +
      +

      Subscription

      +
      + Status + + {query.data.subscription.status.charAt(0).toUpperCase() + query.data.subscription.status.slice(1)} + +
      +
      + Active At + + {new Date(query.data.subscription.currentPeriodStart).toLocaleDateString()} + +
      +
      + Expires At + + {new Date(query.data.subscription.currentPeriodEnd).toLocaleDateString()} + +
      + {query.data.subscription.cancelAtPeriodEnd && ( +
      +

      Will cancel at period end

      +
      + )} +
      + )} +
      + +
      + + +
      + + )} +
      + )} + + )} +
      + ); +} + +export default function OrdersPage() { + return ( + + Loading...
      }> + {() => } + + + ); +} From 0cbb750df4977966d7f225cb880cffb630e73809 Mon Sep 17 00:00:00 2001 From: icyleaf Date: Tue, 10 Mar 2026 15:47:14 +0800 Subject: [PATCH 09/26] refactor: migrate javascript to typescript --- babel.config.js | 3 - .../{sidebars.js => sidebars.ts} | 25 ++- docs/self-hosted/{sidebars.js => sidebars.ts} | 31 +-- docs/user-guide/{sidebars.js => sidebars.ts} | 27 +-- docusaurus.config.js => docusaurus.config.ts | 46 ++-- sidebars.js | 22 -- sidebars.ts | 12 + .../FeatureSection/{index.js => index.tsx} | 22 +- .../HomepageHeader/{index.js => index.tsx} | 8 +- .../{EnterpriseCard.js => EnterpriseCard.tsx} | 10 +- .../{FeaturesList.js => FeaturesList.tsx} | 8 +- .../{PriceAmount.js => PriceAmount.tsx} | 32 +-- .../{PriceButton.js => PriceButton.tsx} | 9 +- .../Pricing/{PriceCards.js => PriceCards.tsx} | 20 +- src/components/Pricing/PriceTitle.js | 38 ---- src/components/Pricing/PriceTitle.tsx | 13 ++ src/components/Pricing/index.js | 57 ----- src/components/Pricing/index.tsx | 89 ++++++++ src/components/ui/Button.tsx | 58 +++++ src/components/ui/Skeleton.js | 15 -- src/components/ui/Skeleton.tsx | 20 ++ .../{pricing-tier.js => pricing-tier.ts} | 59 ++--- src/constants/{regions.js => regions.ts} | 6 +- src/hooks/useGateway.js | 25 --- src/hooks/useGateway.ts | 24 ++ ...{usePaddlePrices.js => usePaddlePrices.ts} | 35 +-- src/lib/{crypto.js => crypto.ts} | 26 ++- src/lib/{gateway.js => gateway.ts} | 20 +- src/lib/query.js | 32 --- src/lib/query.ts | 34 +++ src/lib/{utils.js => utils.ts} | 14 +- src/pages/checkout/{failed.js => failed.tsx} | 37 +-- src/pages/checkout/{index.js => index.tsx} | 107 ++++----- .../checkout/{success.js => success.tsx} | 31 ++- src/pages/{donate.js => donate.tsx} | 52 ++--- src/pages/index.js | 24 -- src/pages/index.tsx | 25 +++ src/pages/{orders/index.js => orders.tsx} | 112 ++++++---- src/pages/pricing.js | 26 --- src/pages/pricing.tsx | 27 +++ src/plugins/tailwind-config.js | 9 - src/plugins/tailwind-config.ts | 15 ++ src/theme/Root/index.js | 14 -- src/theme/Root/index.tsx | 19 ++ src/types/index.ts | 211 ++++++++++++++++++ tsconfig.json | 23 ++ 46 files changed, 953 insertions(+), 619 deletions(-) delete mode 100644 babel.config.js rename docs/developer-guide/{sidebars.js => sidebars.ts} (69%) rename docs/self-hosted/{sidebars.js => sidebars.ts} (69%) rename docs/user-guide/{sidebars.js => sidebars.ts} (80%) rename docusaurus.config.js => docusaurus.config.ts (86%) delete mode 100644 sidebars.js create mode 100644 sidebars.ts rename src/components/FeatureSection/{index.js => index.tsx} (87%) rename src/components/HomepageHeader/{index.js => index.tsx} (96%) rename src/components/Pricing/{EnterpriseCard.js => EnterpriseCard.tsx} (84%) rename src/components/Pricing/{FeaturesList.js => FeaturesList.tsx} (69%) rename src/components/Pricing/{PriceAmount.js => PriceAmount.tsx} (54%) rename src/components/Pricing/{PriceButton.js => PriceButton.tsx} (77%) rename src/components/Pricing/{PriceCards.js => PriceCards.tsx} (78%) delete mode 100644 src/components/Pricing/PriceTitle.js create mode 100644 src/components/Pricing/PriceTitle.tsx delete mode 100644 src/components/Pricing/index.js create mode 100644 src/components/Pricing/index.tsx create mode 100644 src/components/ui/Button.tsx delete mode 100644 src/components/ui/Skeleton.js create mode 100644 src/components/ui/Skeleton.tsx rename src/constants/{pricing-tier.js => pricing-tier.ts} (79%) rename src/constants/{regions.js => regions.ts} (69%) delete mode 100644 src/hooks/useGateway.js create mode 100644 src/hooks/useGateway.ts rename src/hooks/{usePaddlePrices.js => usePaddlePrices.ts} (66%) rename src/lib/{crypto.js => crypto.ts} (73%) rename src/lib/{gateway.js => gateway.ts} (74%) delete mode 100644 src/lib/query.js create mode 100644 src/lib/query.ts rename src/lib/{utils.js => utils.ts} (58%) rename src/pages/checkout/{failed.js => failed.tsx} (67%) rename src/pages/checkout/{index.js => index.tsx} (58%) rename src/pages/checkout/{success.js => success.tsx} (87%) rename src/pages/{donate.js => donate.tsx} (76%) delete mode 100644 src/pages/index.js create mode 100644 src/pages/index.tsx rename src/pages/{orders/index.js => orders.tsx} (77%) delete mode 100644 src/pages/pricing.js create mode 100644 src/pages/pricing.tsx delete mode 100644 src/plugins/tailwind-config.js create mode 100644 src/plugins/tailwind-config.ts delete mode 100644 src/theme/Root/index.js create mode 100644 src/theme/Root/index.tsx create mode 100644 src/types/index.ts create mode 100644 tsconfig.json diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index e00595dae..000000000 --- a/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: [require.resolve('@docusaurus/core/lib/babel/preset')], -}; diff --git a/docs/developer-guide/sidebars.js b/docs/developer-guide/sidebars.ts similarity index 69% rename from docs/developer-guide/sidebars.js rename to docs/developer-guide/sidebars.ts index 9f908290e..c792e691e 100644 --- a/docs/developer-guide/sidebars.js +++ b/docs/developer-guide/sidebars.ts @@ -1,34 +1,33 @@ -module.exports = [ +import type { SidebarItemConfig } from "@docusaurus/plugin-content-docs"; + +const sidebars: SidebarItemConfig[] = [ { label: "Getting Started", type: "doc", - id: 'developer-guide/index', + id: "developer-guide/index", }, { collapsible: false, label: "SDKs", type: "category", link: { - type: 'generated-index', + type: "generated-index", }, - items: [ - "developer-guide/sdk/ios", - "developer-guide/sdk/android" - ], + items: ["developer-guide/sdk/ios", "developer-guide/sdk/android"], }, { collapsible: false, label: "fastlane plugins", type: "category", link: { - type: 'doc', - id: 'developer-guide/fastlane', + type: "doc", + id: "developer-guide/fastlane", }, items: [ "developer-guide/fastlane/zealot", "developer-guide/fastlane/zealot_version_check", "developer-guide/fastlane/zealot_debug_file", - "developer-guide/fastlane/zealot_sync_devices" + "developer-guide/fastlane/zealot_sync_devices", ], }, { @@ -50,7 +49,7 @@ module.exports = [ label: "Local Development", type: "category", link: { - type: 'generated-index', + type: "generated-index", }, items: [ "developer-guide/local-development/devcontainer", @@ -58,5 +57,7 @@ module.exports = [ ], }, ], - } + }, ]; + +export default sidebars; diff --git a/docs/self-hosted/sidebars.js b/docs/self-hosted/sidebars.ts similarity index 69% rename from docs/self-hosted/sidebars.js rename to docs/self-hosted/sidebars.ts index 32a141e81..1caa4da68 100644 --- a/docs/self-hosted/sidebars.js +++ b/docs/self-hosted/sidebars.ts @@ -1,15 +1,17 @@ -module.exports = [ +import type { SidebarItemConfig } from "@docusaurus/plugin-content-docs"; + +const sidebars: SidebarItemConfig[] = [ { label: "Getting Started", type: "doc", - id: 'self-hosted/index', + id: "self-hosted/index", }, { collapsible: false, label: "Deployment", type: "category", link: { - type: 'generated-index', + type: "generated-index", }, items: [ "self-hosted/deployment/architecture", @@ -22,38 +24,36 @@ module.exports = [ type: "doc", id: "self-hosted/deployment/docker", }, - items: [ - "self-hosted/deployment/docker/step-by-step", - ], + items: ["self-hosted/deployment/docker/step-by-step"], }, "self-hosted/deployment/kubernetes", "self-hosted/deployment/nomad", ], }, { - type: 'doc', + type: "doc", label: "Reverse Proxies", - id: 'self-hosted/reverse-proxies', + id: "self-hosted/reverse-proxies", }, { collapsible: false, label: "Cloud Provider Guides", type: "category", link: { - type: 'generated-index', + type: "generated-index", }, items: [ "self-hosted/deployment/fly", "self-hosted/deployment/railway", - "self-hosted/deployment/render" - ] + "self-hosted/deployment/render", + ], }, { collapsible: false, label: "Configuration", type: "category", link: { - type: 'generated-index', + type: "generated-index", }, items: [ "self-hosted/configuration/environment-variables", @@ -62,9 +62,10 @@ module.exports = [ ], }, { - type: 'doc', + type: "doc", label: "Storage", - id: 'self-hosted/storage', + id: "self-hosted/storage", }, - ]; + +export default sidebars; diff --git a/docs/user-guide/sidebars.js b/docs/user-guide/sidebars.ts similarity index 80% rename from docs/user-guide/sidebars.js rename to docs/user-guide/sidebars.ts index ef66cd87c..1d44b47f2 100644 --- a/docs/user-guide/sidebars.js +++ b/docs/user-guide/sidebars.ts @@ -1,20 +1,22 @@ -module.exports = [ +import type { SidebarItemConfig } from "@docusaurus/plugin-content-docs"; + +const sidebars: SidebarItemConfig[] = [ { label: "Getting Started", type: "doc", - id: 'user-guide/index', + id: "user-guide/index", }, { label: "Dashboard", type: "doc", - id: 'user-guide/dashboard', + id: "user-guide/dashboard", }, { collapsible: true, label: "Apps", type: "category", link: { - type: 'generated-index', + type: "generated-index", }, items: [ "user-guide/apps/create", @@ -29,7 +31,7 @@ module.exports = [ label: "Debug Files", type: "category", link: { - type: 'generated-index', + type: "generated-index", }, items: [ "user-guide/debug-files/index", @@ -43,7 +45,7 @@ module.exports = [ type: "category", link: { type: "doc", - id: 'user-guide/webhooks', + id: "user-guide/webhooks", }, items: [ "user-guide/webhooks/slack", @@ -59,19 +61,16 @@ module.exports = [ label: "Toolkits", type: "category", link: { - type: 'generated-index', + type: "generated-index", }, - items: [ - "user-guide/toolkits/teardown", - "user-guide/toolkits/fetch-udid", - ], + items: ["user-guide/toolkits/teardown", "user-guide/toolkits/fetch-udid"], }, { collapsible: false, label: "Administrator", type: "category", link: { - type: 'generated-index', + type: "generated-index", }, items: [ "user-guide/administrator/users", @@ -84,7 +83,7 @@ module.exports = [ label: "Monitoring", type: "category", link: { - type: 'generated-index', + type: "generated-index", }, items: [ "user-guide/administrator/monitoring/system-info", @@ -104,3 +103,5 @@ module.exports = [ "user-guide/qa", "user-guide/credits", ]; + +export default sidebars; diff --git a/docusaurus.config.js b/docusaurus.config.ts similarity index 86% rename from docusaurus.config.js rename to docusaurus.config.ts index 2f00af519..fa49a1c72 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.ts @@ -1,5 +1,7 @@ // @ts-check // Note: type annotations allow type checking and IDEs autocompletion +import type {Config} from '@docusaurus/types'; +import type * as Preset from '@docusaurus/preset-classic'; const isDev = process.env.NODE_ENV === "development"; @@ -33,7 +35,7 @@ function getNextVersionName() { } /** @type {import('@docusaurus/types').Config} */ -const config = { +const config: Config = { title: "Zealot", tagline: "Self-hosted Beta App Distribution for Android, iOS, macOS, Windows and Linux apps.", @@ -58,9 +60,9 @@ const config = { [ "classic", /** @type {import('@docusaurus/preset-classic').Options} */ - ({ + { docs: { - sidebarPath: require.resolve("./sidebars.js"), + sidebarPath: require.resolve("./sidebars.ts"), // Please change this to your repo. // Remove this to remove the "edit this page" links. editUrl: "https://github.com/tryzealot/docs/tree/main/", @@ -83,7 +85,7 @@ const config = { ignorePatterns: ["/tags/**"], filename: "sitemap.xml", }, - }), + } satisfies Preset.Options, ], [ @@ -136,18 +138,6 @@ const config = { position: "left", label: "User Guide", }, - { - type: "doc", - docId: "user-guide/changelog", - position: "left", - label: "Changelog", - }, - { - type: "doc", - docId: "developer-guide/api", - position: "left", - label: "API", - }, { href: "/donate", position: "right", @@ -201,6 +191,14 @@ const config = { label: "GitHub", href: "https://github.com/tryzealot/zealot", }, + { + label: "Changelog", + href: "user-guide/changelog", + }, + { + label: "API", + href: "developer-guide/api", + }, ], }, { @@ -250,17 +248,17 @@ const config = { theme: require("prism-react-renderer").themes.github, darkTheme: require("prism-react-renderer").themes.duotoneDark, }, - // announcementBar: { - // id: 'announcementBar-2', - // backgroundColor: 'var(--ifm-color-primary)', - // textColor: 'var(--ifm-heading-color)', - // content: `⭐️ If you like Zealot, give it a star on GitHub and vote on ProductHunt`, - // isCloseable: true - // }, + announcementBar: { + id: 'announcementBar-2', + backgroundColor: 'var(--ifm-color-primary)', + textColor: 'var(--ifm-heading-color)', + content: `⭐️ If you like Zealot, give it a star on GitHub and vote on ProductHunt`, + isCloseable: true + }, }), plugins: [ - ["./src/plugins/tailwind-config.js", {}], + ["./src/plugins/tailwind-config.ts", {}], [ "docusaurus2-dotenv", { diff --git a/sidebars.js b/sidebars.js deleted file mode 100644 index f7e8e0ffd..000000000 --- a/sidebars.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Creating a sidebar enables you to: - - create an ordered group of docs - - render a sidebar for each doc of that group - - provide next/previous navigation - - The sidebars can be generated from the filesystem, or explicitly defined here. - - Create as many sidebars as you want. - */ - -// @ts-check - -const selfHosted = require("./docs/self-hosted/sidebars"); -const userGuide = require("./docs/user-guide/sidebars"); -const developerGuide = require("./docs/developer-guide/sidebars"); - -module.exports = { - selfHosted, - userGuide, - developerGuide -}; diff --git a/sidebars.ts b/sidebars.ts new file mode 100644 index 000000000..6066b8d2e --- /dev/null +++ b/sidebars.ts @@ -0,0 +1,12 @@ +import type { SidebarsConfig } from "@docusaurus/plugin-content-docs"; +import selfHosted from "./docs/self-hosted/sidebars"; +import userGuide from "./docs/user-guide/sidebars"; +import developerGuide from "./docs/developer-guide/sidebars"; + +const sidebars: SidebarsConfig = { + selfHosted, + userGuide, + developerGuide, +}; + +export default sidebars; diff --git a/src/components/FeatureSection/index.js b/src/components/FeatureSection/index.tsx similarity index 87% rename from src/components/FeatureSection/index.js rename to src/components/FeatureSection/index.tsx index c98b4e45f..6618f0de2 100644 --- a/src/components/FeatureSection/index.js +++ b/src/components/FeatureSection/index.tsx @@ -2,7 +2,15 @@ import React from 'react'; import Translate from '@docusaurus/Translate'; import styles from './styles.module.css'; -const FEATURES_CONFIG = [ +interface FeatureConfig { + icon: string; + titleId: string; + titleDefault: string; + descriptionId: string; + descriptionDefault: string; +} + +const FEATURES_CONFIG: FeatureConfig[] = [ { icon: '🚀', titleId: 'homepage.feature.deployment.title', @@ -47,7 +55,15 @@ const FEATURES_CONFIG = [ }, ]; -function Feature({ icon, titleId, titleDefault, descriptionId, descriptionDefault }) { +interface FeatureProps { + icon: string; + titleId: string; + titleDefault: string; + descriptionId: string; + descriptionDefault: string; +} + +function Feature({ icon, titleId, titleDefault, descriptionId, descriptionDefault }: FeatureProps): JSX.Element { return (
      {icon}
      @@ -69,7 +85,7 @@ function Feature({ icon, titleId, titleDefault, descriptionId, descriptionDefaul ); } -export default function FeatureSection() { +export default function FeatureSection(): JSX.Element { return (
      diff --git a/src/components/HomepageHeader/index.js b/src/components/HomepageHeader/index.tsx similarity index 96% rename from src/components/HomepageHeader/index.js rename to src/components/HomepageHeader/index.tsx index b041e7bde..01ca2df65 100644 --- a/src/components/HomepageHeader/index.js +++ b/src/components/HomepageHeader/index.tsx @@ -2,12 +2,12 @@ import React from 'react'; import clsx from 'clsx'; import Link from '@docusaurus/Link'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import Translate, {translate} from '@docusaurus/Translate'; +import Translate, { translate } from '@docusaurus/Translate'; import ThemedImage from '@theme/ThemedImage'; import styles from './styles.module.css'; -export default function HomepageHeader() { +export default function HomepageHeader(): JSX.Element { const { i18n } = useDocusaurusContext(); const currentLocale = i18n.currentLocale; const imgPath = currentLocale === 'en' ? '/img' : `/img/${currentLocale}`; @@ -45,8 +45,8 @@ export default function HomepageHeader() { - - + +
      🏢
      @@ -16,4 +22,4 @@ export function EnterpriseCard({ enterprise }) {
      ); -} +} \ No newline at end of file diff --git a/src/components/Pricing/FeaturesList.js b/src/components/Pricing/FeaturesList.tsx similarity index 69% rename from src/components/Pricing/FeaturesList.js rename to src/components/Pricing/FeaturesList.tsx index 6555fe295..80ecfd22a 100644 --- a/src/components/Pricing/FeaturesList.js +++ b/src/components/Pricing/FeaturesList.tsx @@ -1,4 +1,8 @@ -export function FeaturesList({ features }) { +interface FeaturesListProps { + features: string[]; +} + +export function FeaturesList({ features }: FeaturesListProps): JSX.Element { return (
        @@ -8,4 +12,4 @@ export function FeaturesList({ features }) {
      ); -} +} \ No newline at end of file diff --git a/src/components/Pricing/PriceAmount.js b/src/components/Pricing/PriceAmount.tsx similarity index 54% rename from src/components/Pricing/PriceAmount.js rename to src/components/Pricing/PriceAmount.tsx index 32ac91e00..1cbe95e1b 100644 --- a/src/components/Pricing/PriceAmount.js +++ b/src/components/Pricing/PriceAmount.tsx @@ -1,6 +1,13 @@ import { Skeleton } from "@site/src/components/ui/Skeleton"; +import type { PricingTierItem } from "../../types"; -export function PriceAmount({ loading, paddlePrice, tier }) { +interface PriceAmountProps { + loading: boolean; + paddlePrice: string | null; + tier: PricingTierItem; +} + +export function PriceAmount({ loading, paddlePrice, tier }: PriceAmountProps): JSX.Element { return ( <>
      @@ -23,25 +30,4 @@ export function PriceAmount({ loading, paddlePrice, tier }) {
      ); - - // return ( - //
      - // {loading ? ( - // - // ) : ( - // <> - //
      - // {priceMap[tier.priceId].replace(/\.00$/, "")} - //
      - //
      - // {priceSuffix} - //
      - // - // )} - //
      - // ); -} +} \ No newline at end of file diff --git a/src/components/Pricing/PriceButton.js b/src/components/Pricing/PriceButton.tsx similarity index 77% rename from src/components/Pricing/PriceButton.js rename to src/components/Pricing/PriceButton.tsx index ebde54317..f80bb99fa 100644 --- a/src/components/Pricing/PriceButton.js +++ b/src/components/Pricing/PriceButton.tsx @@ -1,6 +1,11 @@ import Link from "@docusaurus/Link"; +import type { PricingTierItem } from "../../types"; -export function PricingButton({ tier }) { +interface PricingButtonProps { + tier: PricingTierItem; +} + +export function PricingButton({ tier }: PricingButtonProps): JSX.Element { return ( ); -} +} \ No newline at end of file diff --git a/src/components/Pricing/PriceCards.js b/src/components/Pricing/PriceCards.tsx similarity index 78% rename from src/components/Pricing/PriceCards.js rename to src/components/Pricing/PriceCards.tsx index c7c2b7d69..fc5460ba5 100644 --- a/src/components/Pricing/PriceCards.js +++ b/src/components/Pricing/PriceCards.tsx @@ -8,8 +8,16 @@ import { PriceTitle } from "@site/src/components/Pricing/PriceTitle"; import { PriceAmount } from "@site/src/components/Pricing/PriceAmount"; import { FeaturesList } from "@site/src/components/Pricing/FeaturesList"; import { PricingButton } from "@site/src/components/Pricing/PriceButton"; +import type { PricingTierItem, PaddleInstance } from "../../types"; -export function PriceCard({ tier, paddlePrice, loading }) { +interface PriceCardProps { + tier: PricingTierItem; + paddlePrice: string | null; + loading: boolean; + paddle?: PaddleInstance; +} + +function PriceCard({ tier, paddlePrice, loading }: PriceCardProps): JSX.Element { return (
      PricingTier.filter((tier) => tier.priceId).map((tier) => ({ - priceId: tier.priceId, + priceId: tier.priceId!, quantity: 1, })), [] ); - const { paddle } = usePaddleClient(); + const { paddle } = usePaddleClient({} as Parameters[0]); const { prices, loading } = usePaddlePrices(paddle, items); return (
      {PricingTier.map((tier) => { - const paddlePrice = prices[tier.priceId] + const paddlePrice = tier.priceId && prices[tier.priceId] ? prices[tier.priceId].replace(/\.00$/, "") : null; return ( @@ -57,4 +65,4 @@ export function PriceCards() { })}
      ); -} +} \ No newline at end of file diff --git a/src/components/Pricing/PriceTitle.js b/src/components/Pricing/PriceTitle.js deleted file mode 100644 index c0b33bf39..000000000 --- a/src/components/Pricing/PriceTitle.js +++ /dev/null @@ -1,38 +0,0 @@ -import { cn } from "@site/src/lib/utils"; - -export function PriceTitle({ tier }) { - const { name, featured, icon } = tier; - return ( -
      - {tier.icon} {tier.name} -
      - ); -} - -{ - /*
      -
      -
      - {icon} -
      -

      {name}

      -
      - {featured && ( -
      - Most popular -
      - )} -
      */ -} diff --git a/src/components/Pricing/PriceTitle.tsx b/src/components/Pricing/PriceTitle.tsx new file mode 100644 index 000000000..6187f6978 --- /dev/null +++ b/src/components/Pricing/PriceTitle.tsx @@ -0,0 +1,13 @@ +import type { PricingTierItem } from "../../types"; + +interface PriceTitleProps { + tier: PricingTierItem; +} + +export function PriceTitle({ tier }: PriceTitleProps): JSX.Element { + return ( +
      + {tier.icon} {tier.name} +
      + ); +} \ No newline at end of file diff --git a/src/components/Pricing/index.js b/src/components/Pricing/index.js deleted file mode 100644 index f791f1a8a..000000000 --- a/src/components/Pricing/index.js +++ /dev/null @@ -1,57 +0,0 @@ -import { useEffect, useState } from "react"; -import { initializePaddle } from "@paddle/paddle-js"; -import { usePaddlePrices } from "@site/src/hooks/usePaddlePrices"; -import { PriceCards } from "@site/src/components/Pricing/PriceCards"; -import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; - -// Insipred by https://github.com/PaddleHQ/paddle-nextjs-starter-kit - -export function Pricing({ country }) { - console.log("Rendering Pricing component", country); - const [paddle, setPaddle] = useState(undefined); - const { prices, loading } = usePaddlePrices(paddle, country); - - useEffect(() => { - if (process.env.PADDLE_CLIENT_TOKEN && process.env.PADDLE_ENV) { - initializePaddle({ - token: process.env.PADDLE_CLIENT_TOKEN, - environment: process.env.PADDLE_ENV, - }).then((paddle) => { - if (paddle) { - setPaddle(paddle); - console.log("Paddle initialized"); - } - }); - } - }, []); - - return ( -
      -
      -
      - {PricingTier.map((tier) => { - const paddlePrice = prices[tier.priceId] - ? prices[tier.priceId].replace(/\.00$/, "") - : null; - return ( -
      - -
      - ); - })} -
      -
      -
      - -
      -
      - ); -} diff --git a/src/components/Pricing/index.tsx b/src/components/Pricing/index.tsx new file mode 100644 index 000000000..6f0bb52e6 --- /dev/null +++ b/src/components/Pricing/index.tsx @@ -0,0 +1,89 @@ +import { useEffect, useState } from "react"; +import { initializePaddle } from "@paddle/paddle-js"; +import { usePaddlePrices } from "@site/src/hooks/usePaddlePrices"; +import { PriceCards } from "@site/src/components/Pricing/PriceCards"; +import { EnterpriseCard } from "@site/src/components/Pricing/EnterpriseCard"; +import { PricingTier, EnterpriseTier } from "@site/src/constants/pricing-tier"; +import { PriceTitle } from "@site/src/components/Pricing/PriceTitle"; +import { PriceAmount } from "@site/src/components/Pricing/PriceAmount"; +import { FeaturesList } from "@site/src/components/Pricing/FeaturesList"; +import { PricingButton } from "@site/src/components/Pricing/PriceButton"; +import type { PaddleInstance, PricingTierItem, PaddleCheckoutItem } from "../../types"; + +interface PricingCardProps { + tier: PricingTierItem; + paddlePrice: string | null; + loading: boolean; + paddle?: PaddleInstance; +} + +function PricingCard({ tier, paddlePrice, loading }: PricingCardProps): JSX.Element { + return ( +
      + + + + +
      + ); +} + +interface PricingProps { + country?: PaddleCheckoutItem[]; +} + +export function Pricing({ country }: PricingProps): JSX.Element { + console.log("Rendering Pricing component", country); + const [paddle, setPaddle] = useState(undefined); + const { prices, loading } = usePaddlePrices(paddle, country || []); + + useEffect(() => { + if (process.env.PADDLE_CLIENT_TOKEN && process.env.PADDLE_ENV) { + initializePaddle({ + token: process.env.PADDLE_CLIENT_TOKEN, + environment: process.env.PADDLE_ENV, + }).then((paddle) => { + if (paddle) { + setPaddle(paddle as unknown as PaddleInstance); + console.log("Paddle initialized"); + } + }); + } + }, []); + + return ( +
      +
      +
      + {PricingTier.map((tier) => { + const paddlePrice = tier.priceId && prices[tier.priceId] + ? prices[tier.priceId].replace(/\.00$/, "") + : null; + return ( +
      + +
      + ); + })} +
      +
      +
      + +
      +
      + ); +} \ No newline at end of file diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx new file mode 100644 index 000000000..94bd8049b --- /dev/null +++ b/src/components/ui/Button.tsx @@ -0,0 +1,58 @@ +import React from "react"; + +interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: "primary" | "secondary"; + isLoading?: boolean; + fullWidth?: boolean; +} + +export function Button({ + children, + variant = "primary", + isLoading = false, + fullWidth = true, + className = "", + disabled, + ...props +}: ButtonProps): JSX.Element { + const baseStyles = "hover:cursor-pointer px-6 py-3 text-lg font-semibold rounded-lg transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-[--ifm-color-primary] focus:ring-offset-2 dark:focus:ring-offset-gray-900"; + + const variantStyles = { + primary: "text-white shadow-lg hover:shadow-xl hover:scale-[1.02] hover:-translate-y-0.5 active:scale-[0.98]", + secondary: "text-[--ifm-color-primary] bg-white border-2 border-[--ifm-color-primary] shadow-sm hover:shadow-md hover:bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700", + }; + + const widthStyles = fullWidth ? "w-full" : ""; + const disabledStyles = disabled || isLoading ? "opacity-50 cursor-not-allowed" : ""; + + return ( + + ); +} + +interface PrimaryButtonProps extends Omit {} + +export function PrimaryButton({ className = "", ...props }: PrimaryButtonProps): JSX.Element { + return ( + + Continue ) : ( <> @@ -173,35 +176,9 @@ const checkoutOptions = { )} {query.data && query.data.orders?.length > 0 && ( -
      -
      -
      - - - -

      Existing Orders Found

      -
      -

      - You have {query.data.orders.length} order(s) associated with this email. View your complete order history, license details, and subscription information. -

      -
      - - -
      -
      +
      +
      +

      Redirecting to your orders...

      )} @@ -210,7 +187,7 @@ const checkoutOptions = { ); } -export default function CheckoutPage() { +export default function CheckoutPage(): JSX.Element { return ( Loading checkout...
      }> @@ -218,4 +195,4 @@ export default function CheckoutPage() { ); -} +} \ No newline at end of file diff --git a/src/pages/checkout/success.js b/src/pages/checkout/success.tsx similarity index 87% rename from src/pages/checkout/success.js rename to src/pages/checkout/success.tsx index f566e29da..656812359 100644 --- a/src/pages/checkout/success.js +++ b/src/pages/checkout/success.tsx @@ -1,18 +1,18 @@ import Layout from "@theme/Layout"; import BrowserOnly from "@docusaurus/BrowserOnly"; import { useEffect, useState } from "react"; -import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; import { useGateway } from "@site/src/hooks/useGateway"; +import type { LicenseResponse, TransactionData } from "@site/src/types"; -function SuccessClient() { +function SuccessClient(): JSX.Element { const { gateway } = useGateway(); const [isLoading, setIsLoading] = useState(true); - const [license, setLicense] = useState(null); - const [error, setError] = useState(null); - const [transactionInfo, setTransactionInfo] = useState(null); + const [license, setLicense] = useState(null); + const [error, setError] = useState(null); + const [transactionInfo, setTransactionInfo] = useState(null); const [copied, setCopied] = useState(false); - const copyToClipboard = async (text) => { + const copyToClipboard = async (text: string): Promise => { try { await navigator.clipboard.writeText(text); setCopied(true); @@ -23,20 +23,16 @@ function SuccessClient() { }; useEffect(() => { - // 从 sessionStorage 读取交易数据 const storedData = sessionStorage.getItem("paddleTransaction"); if (storedData) { - const transactionData = JSON.parse(storedData); + const transactionData: TransactionData = JSON.parse(storedData); setTransactionInfo(transactionData); - // 清除 sessionStorage 中的数据 - // sessionStorage.removeItem("paddleTransaction"); } else { setError("No transaction information found"); setIsLoading(false); } }, []); - // 当 gateway 初始化完成且有交易数据时,生成 license useEffect(() => { if (gateway && transactionInfo && !license && !error && isLoading) { const data = { @@ -53,13 +49,12 @@ function SuccessClient() { console.log("License API response:", result); if (result.success) { setLicense(result); - setIsLoading(false); } else { setError(result.message || "Failed to generate license"); - setIsLoading(false); } + setIsLoading(false); }) - .catch((err) => { + .catch((err: Error) => { console.error("Failed to generate license:", err); setError(err.message || "Failed to generate license"); setIsLoading(false); @@ -115,7 +110,7 @@ function SuccessClient() {

      License Key

      {license.license?.key}
      @@ -165,7 +160,7 @@ function SuccessClient() { ); } -export default function CheckoutSuccessPage() { +export default function CheckoutSuccessPage(): JSX.Element { return ( Loading...
      }> @@ -173,4 +168,4 @@ export default function CheckoutSuccessPage() { ); -} +} \ No newline at end of file diff --git a/src/pages/donate.js b/src/pages/donate.tsx similarity index 76% rename from src/pages/donate.js rename to src/pages/donate.tsx index c8cc22c16..fb5ce6bb4 100644 --- a/src/pages/donate.js +++ b/src/pages/donate.tsx @@ -1,15 +1,25 @@ - - +import React, { CSSProperties } from 'react'; import Layout from '@theme/Layout'; import useBaseUrl from '@docusaurus/useBaseUrl'; -import Translate, {translate} from '@docusaurus/Translate'; +import Translate, { translate } from '@docusaurus/Translate'; + +const linkStyle: CSSProperties = { + display: 'block', + fontWeight: 600, + fontSize: 18, + borderRadius: 8, + padding: '16px 0', + textAlign: 'center', + textDecoration: 'none', + boxShadow: '0 2px 8px rgba(123,111,240,0.08)', +}; -export default function Donate() { +export default function Donate(): JSX.Element { return ( + id: 'donate.title', + message: 'Support Zealot', + })}>

      Support Zealot

      @@ -25,16 +35,9 @@ export default function Donate() { target="_blank" rel="noopener noreferrer" style={{ - display: 'block', + ...linkStyle, background: '#7b6ff0', color: '#fff', - fontWeight: 600, - fontSize: 18, - borderRadius: 8, - padding: '16px 0', - textAlign: 'center', - textDecoration: 'none', - boxShadow: '0 2px 8px rgba(123,111,240,0.08)' }} > @@ -45,16 +48,9 @@ export default function Donate() { target="_blank" rel="noopener noreferrer" style={{ - display: 'block', + ...linkStyle, background: '#F8D802', color: '#000', - fontWeight: 600, - fontSize: 18, - borderRadius: 8, - padding: '16px 0', - textAlign: 'center', - textDecoration: 'none', - boxShadow: '0 2px 8px rgba(123,111,240,0.08)' }} > @@ -65,16 +61,10 @@ export default function Donate() { target="_blank" rel="noopener noreferrer" style={{ - display: 'block', + ...linkStyle, background: '#ff4eb5', color: '#fff', - fontWeight: 600, - fontSize: 18, - borderRadius: 8, - padding: '16px 0', - textAlign: 'center', - textDecoration: 'none', - boxShadow: '0 2px 8px rgba(255,78,181,0.08)' + boxShadow: '0 2px 8px rgba(255,78,181,0.08)', }} > diff --git a/src/pages/index.js b/src/pages/index.js deleted file mode 100644 index fbe7d94b8..000000000 --- a/src/pages/index.js +++ /dev/null @@ -1,24 +0,0 @@ -import Layout from "@theme/Layout"; -import { translate } from "@docusaurus/Translate"; -import HomepageHeader from "@site/src/components/HomepageHeader"; -import FeatureSection from "@site/src/components/FeatureSection"; - -export default function Home() { - return ( - - - - - ); -} diff --git a/src/pages/index.tsx b/src/pages/index.tsx new file mode 100644 index 000000000..7ecfdce69 --- /dev/null +++ b/src/pages/index.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import Layout from '@theme/Layout'; +import { translate } from '@docusaurus/Translate'; +import HomepageHeader from '@site/src/components/HomepageHeader'; +import FeatureSection from '@site/src/components/FeatureSection'; + +export default function Home(): JSX.Element { + return ( + + + + + ); +} diff --git a/src/pages/orders/index.js b/src/pages/orders.tsx similarity index 77% rename from src/pages/orders/index.js rename to src/pages/orders.tsx index acbd9bd7e..d818038df 100644 --- a/src/pages/orders/index.js +++ b/src/pages/orders.tsx @@ -1,25 +1,61 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import Layout from "@theme/Layout"; import BrowserOnly from "@docusaurus/BrowserOnly"; import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; import { useHistory } from "@docusaurus/router"; import { useGateway } from "@site/src/hooks/useGateway"; import { useCustomerOrders } from "@site/src/lib/query"; +import { decrypt } from "@site/src/lib/crypto"; +import { PrimaryButton, SecondaryButton } from "@site/src/components/ui/Button"; +import type { Order, License, Subscription, Customer } from "@site/src/types"; -function OrdersClient() { +function OrdersClient(): JSX.Element { const { i18n } = useDocusaurusContext(); const history = useHistory(); const [userEmail, setUserEmail] = useState(""); const [submittedEmail, setSubmittedEmail] = useState(""); const [hasSubmitted, setHasSubmitted] = useState(false); + const [fromCheckout, setFromCheckout] = useState(false); + + // 从 sessionStorage 读取预填充的 email(从 checkout 页面跳转过来) + useEffect(() => { + const storedEmail = sessionStorage.getItem("ordersEmail"); + if (storedEmail) { + const secretKey = process.env.ZEALOT_ENCRYPTION_KEY; + if (secretKey) { + decrypt(storedEmail, secretKey) + .then((decryptedEmail) => { + setUserEmail(decryptedEmail); + setSubmittedEmail(decryptedEmail); + setHasSubmitted(true); + setFromCheckout(true); + sessionStorage.removeItem("ordersEmail"); + }) + .catch(() => { + // 解密失败,可能是未加密的旧数据 + setUserEmail(storedEmail); + setSubmittedEmail(storedEmail); + setHasSubmitted(true); + setFromCheckout(true); + sessionStorage.removeItem("ordersEmail"); + }); + } else { + setUserEmail(storedEmail); + setSubmittedEmail(storedEmail); + setHasSubmitted(true); + setFromCheckout(true); + sessionStorage.removeItem("ordersEmail"); + } + } + }, []); const basePath = i18n.currentLocale !== "en" ? `/${i18n.currentLocale}` : ""; const { gateway } = useGateway(); const query = useCustomerOrders(gateway, submittedEmail, hasSubmitted); - const handleEmailSubmit = (e) => { + const handleEmailSubmit = (e: React.FormEvent) => { e.preventDefault(); if (userEmail.trim()) { setSubmittedEmail(userEmail.trim()); @@ -44,22 +80,10 @@ function OrdersClient() { className="w-full px-5 py-3 text-lg border-2 border-gray-300 rounded-lg focus:outline-none focus:border-[--ifm-color-primary] dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:focus:border-[--ifm-color-primary] transition-colors" required /> - - + ) : ( <> @@ -68,12 +92,9 @@ function OrdersClient() {

      Error loading your orders.

      - +
) : (query.isLoading || !query.data) && (
@@ -88,6 +109,13 @@ function OrdersClient() {

Your Orders

{submittedEmail}
+ {fromCheckout && query.data.orders && query.data.orders.length > 0 && ( +
+

+ You already have an order associated with this email. +

+
+ )} {query.data.orders?.length === 0 ? (
@@ -95,21 +123,12 @@ function OrdersClient() {

No orders found for this email.

- - +
) : ( @@ -152,7 +171,7 @@ function OrdersClient() { )} {/* Orders */} - {query.data.orders.map((order) => ( + {query.data.orders.map((order: Order) => (
- - +
)} @@ -269,7 +285,7 @@ function OrdersClient() { ); } -export default function OrdersPage() { +export default function OrdersPage(): JSX.Element { return ( Loading...
}> @@ -277,4 +293,4 @@ export default function OrdersPage() { ); -} +} \ No newline at end of file diff --git a/src/pages/pricing.js b/src/pages/pricing.js deleted file mode 100644 index d4caec95b..000000000 --- a/src/pages/pricing.js +++ /dev/null @@ -1,26 +0,0 @@ -import Layout from "@theme/Layout"; -import { translate } from "@docusaurus/Translate"; -import { EnterpriseTier } from "@site/src/constants/pricing-tier"; -import { EnterpriseCard } from "@site/src/components/Pricing/EnterpriseCard"; -import { PriceCards } from "@site/src/components/Pricing/PriceCards"; - -export default function PricingPage() { - return ( - -
- - -
-
- ); -} diff --git a/src/pages/pricing.tsx b/src/pages/pricing.tsx new file mode 100644 index 000000000..d83fb99a4 --- /dev/null +++ b/src/pages/pricing.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import Layout from '@theme/Layout'; +import { translate } from '@docusaurus/Translate'; +import { EnterpriseTier } from '@site/src/constants/pricing-tier'; +import { EnterpriseCard } from '@site/src/components/Pricing/EnterpriseCard'; +import { PriceCards } from '@site/src/components/Pricing/PriceCards'; + +export default function PricingPage(): JSX.Element { + return ( + +
+ + +
+
+ ); +} diff --git a/src/plugins/tailwind-config.js b/src/plugins/tailwind-config.js deleted file mode 100644 index b02f9541f..000000000 --- a/src/plugins/tailwind-config.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = function tailwindPlugin(context, options) { - return { - name: "tailwind-plugin", - configurePostCss(postcssOptions) { - postcssOptions.plugins = [require("@tailwindcss/postcss")]; - return postcssOptions; - }, - }; -}; diff --git a/src/plugins/tailwind-config.ts b/src/plugins/tailwind-config.ts new file mode 100644 index 000000000..66d51bd6d --- /dev/null +++ b/src/plugins/tailwind-config.ts @@ -0,0 +1,15 @@ +import type { LoadContext, Plugin } from '@docusaurus/types'; +import type { PostCssOptions } from '@docusaurus/types'; + +export default function tailwindPlugin( + context: LoadContext, + options: Record +): Plugin { + return { + name: 'tailwind-plugin', + configurePostCss(postcssOptions: PostCssOptions): PostCssOptions { + postcssOptions.plugins = [require('@tailwindcss/postcss')]; + return postcssOptions; + }, + }; +} diff --git a/src/theme/Root/index.js b/src/theme/Root/index.js deleted file mode 100644 index 5bf41f190..000000000 --- a/src/theme/Root/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; - -const queryClient = new QueryClient(); - -export default function Root({ children }) { - const env = process.env.ENVIRONMENT || "production"; - return ( - - <>{children} - {env === "development" && } - - ); -} diff --git a/src/theme/Root/index.tsx b/src/theme/Root/index.tsx new file mode 100644 index 000000000..640f500b1 --- /dev/null +++ b/src/theme/Root/index.tsx @@ -0,0 +1,19 @@ +import React, { ReactNode } from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; + +const queryClient = new QueryClient(); + +interface RootProps { + children: ReactNode; +} + +export default function Root({ children }: RootProps): JSX.Element { + const env = process.env.ENVIRONMENT || 'production'; + return ( + + <>{children} + {env === 'development' && } + + ); +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 000000000..990299057 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,211 @@ +// ===== Gateway 相关类型 ===== +export interface GatewayClient { + orders: (encryptedEmail: string) => Promise; + generateLicense: (data: LicenseRequest) => Promise; +} + +// ===== 订单相关类型 ===== +export interface Customer { + id: string; + email: string; +} + +export interface License { + id: string; + key: string; + active: boolean; +} + +export interface Order { + id: string; + totalAmount: number; + currency: string; + status: 'completed' | 'pending' | 'failed'; + createdAt: string; + paidAt?: string; +} + +export interface Subscription { + id: string; + status: 'active' | 'cancelled' | 'paused' | 'trialing'; + currentPeriodStart: string; + currentPeriodEnd: string; + cancelAtPeriodEnd: boolean; +} + +export interface OrdersResponse { + customer?: Customer; + license?: License; + orders: Order[]; + subscription?: Subscription; +} + +// ===== License 相关类型 ===== +export interface LicenseRequest { + transactionId: string; + checkoutId: string; + customerEmail: string; + priceId: string; +} + +export interface LicenseData { + key: string; + expiredAt: string; +} + +export interface PurchaseData { + id: string; + planType: string; + productType: string; +} + +export interface LicenseResponse { + success: boolean; + message?: string; + license?: LicenseData; + purchase?: PurchaseData; + customer?: Customer; +} + +// ===== Paddle 相关类型 ===== +export interface PaddleCheckoutSettings { + variant: 'multi-page' | 'inline'; + locale: string; + theme: 'dark' | 'light'; + allowLogout: boolean; + showAddTaxId: boolean; + successUrl: string; + displayMode?: 'inline' | 'overlay'; + frameTarget?: string; + frameStyle?: string; + frameInitialHeight?: string; +} + +export interface PaddleCheckoutOptions { + checkout: { + settings: PaddleCheckoutSettings; + }; + eventCallback: (data: PaddleEvent) => void; +} + +export interface PaddleEventData { + id: string; + transaction_id?: string; + customer?: { + email: string; + name?: string; + }; + items?: Array<{ + price_id: string; + }>; + totals?: { + total: number; + }; + error?: { + code: string; + message: string; + }; +} + +export interface PaddleEvent { + name: 'checkout.completed' | 'checkout.payment.failed' | 'checkout.closed' | 'checkout.customer.updated' | string; + data: PaddleEventData; +} + +export interface PaddleCheckoutItem { + priceId: string; + quantity?: number; +} + +export interface PaddleCheckoutOpenOptions { + customer: { + email: string; + name: string; + address: { + countryCode: string; + }; + }; + discountCode?: string | null; + items: PaddleCheckoutItem[]; + customData: { + customerEmail: string; + priceId: string; + }; +} + +export interface PaddleInstance { + Initialized: boolean; + Checkout: { + open: (options: PaddleCheckoutOpenOptions) => void; + }; + Spinner: { + show: () => void; + hide?: () => void; + }; + PricePreview: (request: { items: PaddleCheckoutItem[]; country: string }) => Promise; +} + +export interface PaddlePricePreviewItem { + price: { id: string }; + formattedTotals: { total: string }; +} + +export interface PaddlePricePreviewResponse { + data: { + details: { + lineItems: PaddlePricePreviewItem[]; + }; + }; +} + +// ===== 交易数据类型 ===== +export interface TransactionData { + transactionId: string; + checkoutId: string; + customerEmail: string; + priceId: string; + amount?: number; +} + +export interface ErrorData { + errorCode: string; + errorMessage: string; +} + +// ===== Pricing 相关类型 ===== +export interface PricingButtonConfig { + text: string; + id?: string; + href?: string; + type: 'link' | 'paddle'; +} + +export interface PricingTierItem { + key: string; + name: string; + icon: string; + price?: string; + priceId?: string; + frequency?: string; + trial?: string; + save?: string; + features: string[]; + button: PricingButtonConfig; + highlight: boolean; +} + +export interface EnterpriseTierItem { + title: string; + description: string; + button: { + text: string; + href: string; + }; +} + +// ===== Region 相关类型 ===== +export interface Region { + locale: string; + value: string; + label: string; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..79c87b337 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "@docusaurus/tsconfig", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@site/*": ["./*"] + }, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "target": "ES2020", + "types": ["node", "@docusaurus/module-type-aliases"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "build", ".docusaurus"] +} \ No newline at end of file From 5a8acda5876ad55d3c00b40de947f7480312f4e3 Mon Sep 17 00:00:00 2001 From: icyleaf Date: Tue, 10 Mar 2026 20:55:13 +0800 Subject: [PATCH 10/26] style: uew daisyui like theme --- docusaurus.config.ts | 2 +- .../docusaurus-theme-classic/config.json | 10 + pnpm-lock.yaml | 2 +- src/components/Pricing/EnterpriseCard.tsx | 12 +- src/components/Pricing/FeaturesList.tsx | 4 +- src/components/Pricing/PriceAmount.tsx | 10 +- src/components/Pricing/PriceButton.tsx | 6 +- src/components/Pricing/PriceCards.tsx | 10 +- src/components/Pricing/PriceTitle.tsx | 6 +- src/components/Pricing/index.tsx | 2 +- src/components/ui/Button.tsx | 57 +++-- src/components/ui/Skeleton.tsx | 4 +- src/constants/pricing-tier.ts | 2 +- src/constants/regions.ts | 2 +- src/css/custom.css | 229 ++++++++++++++---- src/hooks/useGateway.ts | 2 +- src/hooks/usePaddlePrices.ts | 2 +- src/lib/gateway.ts | 2 +- src/lib/query.ts | 4 +- src/pages/checkout/failed.tsx | 29 +-- src/pages/checkout/index.tsx | 20 +- src/pages/checkout/success.tsx | 71 +++--- src/pages/index.tsx | 1 - src/pages/orders.tsx | 104 ++++---- 24 files changed, 380 insertions(+), 213 deletions(-) create mode 100644 i18n/zh-Hans/docusaurus-theme-classic/config.json diff --git a/docusaurus.config.ts b/docusaurus.config.ts index fa49a1c72..7a8f2b54f 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -249,7 +249,7 @@ const config: Config = { darkTheme: require("prism-react-renderer").themes.duotoneDark, }, announcementBar: { - id: 'announcementBar-2', + id: 'announcementBar', backgroundColor: 'var(--ifm-color-primary)', textColor: 'var(--ifm-heading-color)', content: `⭐️ If you like Zealot, give it a star on GitHub and vote on ProductHunt`, diff --git a/i18n/zh-Hans/docusaurus-theme-classic/config.json b/i18n/zh-Hans/docusaurus-theme-classic/config.json new file mode 100644 index 000000000..b36af288a --- /dev/null +++ b/i18n/zh-Hans/docusaurus-theme-classic/config.json @@ -0,0 +1,10 @@ +{ + "theme.announcementBar.content": { + "message": "Nous cherchons à réorganiser notre documentation, veuillez remplir ce sondage", + "description": "The content of the announcement bar" + }, + "theme.announcementBar.closeButton": { + "message": "关闭", + "description": "The announcement bar close button aria label" + } +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fab53a725..c96fec850 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7637,7 +7637,7 @@ snapshots: '@docusaurus/react-loadable@6.0.0(react@19.0.0)': dependencies: - '@types/react': 19.1.12 + '@types/react': 19.2.9 react: 19.0.0 '@docusaurus/theme-classic@3.9.2(@types/react@19.2.9)(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': diff --git a/src/components/Pricing/EnterpriseCard.tsx b/src/components/Pricing/EnterpriseCard.tsx index 4c76060d4..d4eeb6330 100644 --- a/src/components/Pricing/EnterpriseCard.tsx +++ b/src/components/Pricing/EnterpriseCard.tsx @@ -1,4 +1,4 @@ -import type { EnterpriseTierItem } from "../../types"; +import type { EnterpriseTierItem } from "@site/src/types"; interface EnterpriseCardProps { enterprise: EnterpriseTierItem; @@ -6,14 +6,14 @@ interface EnterpriseCardProps { export function EnterpriseCard({ enterprise }: EnterpriseCardProps): JSX.Element { return ( -
+
🏢
-
{enterprise.title}
-
+
{enterprise.title}
+
{enterprise.description}
); -} \ No newline at end of file +} diff --git a/src/components/Pricing/FeaturesList.tsx b/src/components/Pricing/FeaturesList.tsx index 80ecfd22a..820ca344c 100644 --- a/src/components/Pricing/FeaturesList.tsx +++ b/src/components/Pricing/FeaturesList.tsx @@ -5,11 +5,11 @@ interface FeaturesListProps { export function FeaturesList({ features }: FeaturesListProps): JSX.Element { return (
-
    +
      {features.map((f) => (
    • {f}
    • ))}
); -} \ No newline at end of file +} diff --git a/src/components/Pricing/PriceAmount.tsx b/src/components/Pricing/PriceAmount.tsx index 1cbe95e1b..d5f95fadf 100644 --- a/src/components/Pricing/PriceAmount.tsx +++ b/src/components/Pricing/PriceAmount.tsx @@ -1,5 +1,5 @@ import { Skeleton } from "@site/src/components/ui/Skeleton"; -import type { PricingTierItem } from "../../types"; +import type { PricingTierItem } from "@site/src/types"; interface PriceAmountProps { loading: boolean; @@ -14,20 +14,20 @@ export function PriceAmount({ loading, paddlePrice, tier }: PriceAmountProps): J {loading ? ( ) : ( - + {paddlePrice || tier.price}{" "} {paddlePrice && tier.frequency && ` / ${tier.frequency}`} )}
{tier.save && ( -
+
{tier.save}
)} -
+
{tier.trial}
); -} \ No newline at end of file +} diff --git a/src/components/Pricing/PriceButton.tsx b/src/components/Pricing/PriceButton.tsx index f80bb99fa..5b36bf246 100644 --- a/src/components/Pricing/PriceButton.tsx +++ b/src/components/Pricing/PriceButton.tsx @@ -1,5 +1,5 @@ import Link from "@docusaurus/Link"; -import type { PricingTierItem } from "../../types"; +import type { PricingTierItem } from "@site/src/types"; interface PricingButtonProps { tier: PricingTierItem; @@ -9,7 +9,7 @@ export function PricingButton({ tier }: PricingButtonProps): JSX.Element { return ( ); -} \ No newline at end of file +} diff --git a/src/components/Pricing/PriceCards.tsx b/src/components/Pricing/PriceCards.tsx index fc5460ba5..379a455a2 100644 --- a/src/components/Pricing/PriceCards.tsx +++ b/src/components/Pricing/PriceCards.tsx @@ -8,7 +8,7 @@ import { PriceTitle } from "@site/src/components/Pricing/PriceTitle"; import { PriceAmount } from "@site/src/components/Pricing/PriceAmount"; import { FeaturesList } from "@site/src/components/Pricing/FeaturesList"; import { PricingButton } from "@site/src/components/Pricing/PriceButton"; -import type { PricingTierItem, PaddleInstance } from "../../types"; +import type { PricingTierItem, PaddleInstance } from "@site/src/types"; interface PriceCardProps { tier: PricingTierItem; @@ -20,11 +20,11 @@ interface PriceCardProps { function PriceCard({ tier, paddlePrice, loading }: PriceCardProps): JSX.Element { return (
@@ -65,4 +65,4 @@ export function PriceCards(): JSX.Element { })}
); -} \ No newline at end of file +} diff --git a/src/components/Pricing/PriceTitle.tsx b/src/components/Pricing/PriceTitle.tsx index 6187f6978..14b547648 100644 --- a/src/components/Pricing/PriceTitle.tsx +++ b/src/components/Pricing/PriceTitle.tsx @@ -1,4 +1,4 @@ -import type { PricingTierItem } from "../../types"; +import type { PricingTierItem } from "@site/src/types"; interface PriceTitleProps { tier: PricingTierItem; @@ -6,8 +6,8 @@ interface PriceTitleProps { export function PriceTitle({ tier }: PriceTitleProps): JSX.Element { return ( -
+
{tier.icon} {tier.name}
); -} \ No newline at end of file +} diff --git a/src/components/Pricing/index.tsx b/src/components/Pricing/index.tsx index 6f0bb52e6..53bf080fb 100644 --- a/src/components/Pricing/index.tsx +++ b/src/components/Pricing/index.tsx @@ -8,7 +8,7 @@ import { PriceTitle } from "@site/src/components/Pricing/PriceTitle"; import { PriceAmount } from "@site/src/components/Pricing/PriceAmount"; import { FeaturesList } from "@site/src/components/Pricing/FeaturesList"; import { PricingButton } from "@site/src/components/Pricing/PriceButton"; -import type { PaddleInstance, PricingTierItem, PaddleCheckoutItem } from "../../types"; +import type { PaddleInstance, PricingTierItem, PaddleCheckoutItem } from "@site/src/types"; interface PricingCardProps { tier: PricingTierItem; diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx index 94bd8049b..72de307b1 100644 --- a/src/components/ui/Button.tsx +++ b/src/components/ui/Button.tsx @@ -1,7 +1,8 @@ import React from "react"; interface ButtonProps extends React.ButtonHTMLAttributes { - variant?: "primary" | "secondary"; + variant?: "primary" | "secondary" | "accent" | "neutral" | "ghost"; + size?: "sm" | "md" | "lg"; isLoading?: boolean; fullWidth?: boolean; } @@ -9,17 +10,27 @@ interface ButtonProps extends React.ButtonHTMLAttributes { export function Button({ children, variant = "primary", + size = "md", isLoading = false, fullWidth = true, className = "", disabled, ...props }: ButtonProps): JSX.Element { - const baseStyles = "hover:cursor-pointer px-6 py-3 text-lg font-semibold rounded-lg transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-[--ifm-color-primary] focus:ring-offset-2 dark:focus:ring-offset-gray-900"; + const baseStyles = "hover:cursor-pointer rounded-lg transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-[var(--color-base-100)] font-semibold"; + + const sizeStyles = { + sm: "px-4 py-2 text-sm", + md: "px-6 py-3 text-base", + lg: "px-8 py-4 text-lg", + }; const variantStyles = { - primary: "text-white shadow-lg hover:shadow-xl hover:scale-[1.02] hover:-translate-y-0.5 active:scale-[0.98]", - secondary: "text-[--ifm-color-primary] bg-white border-2 border-[--ifm-color-primary] shadow-sm hover:shadow-md hover:bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700", + primary: "bg-[var(--color-primary)] text-[var(--color-primary-content)] border-2 border-transparent hover:opacity-90 focus:ring-[var(--color-primary)] shadow-lg hover:shadow-xl active:scale-[0.98]", + secondary: "bg-[var(--color-secondary)] text-[var(--color-secondary-content)] border-2 border-transparent hover:opacity-90 focus:ring-[var(--color-secondary)] shadow-md hover:shadow-lg", + accent: "bg-[var(--color-accent)] text-[var(--color-accent-content)] border-2 border-transparent hover:opacity-90 focus:ring-[var(--color-accent)] shadow-lg hover:shadow-xl hover:scale-[1.02] active:scale-[0.98]", + neutral: "bg-[var(--color-neutral)] text-[var(--color-neutral-content)] border-2 border-transparent hover:opacity-90 focus:ring-[var(--color-neutral)]", + outline: "bg-transparent text-[var(--color-primary)] border-2 border-[var(--color-primary)] hover:bg-[var(--color-primary)] hover:text-[var(--color-primary-content)] focus:ring-[var(--color-primary)]", }; const widthStyles = fullWidth ? "w-full" : ""; @@ -27,11 +38,18 @@ export function Button({ return ( ); } @@ -39,20 +57,23 @@ export function Button({ interface PrimaryButtonProps extends Omit {} export function PrimaryButton({ className = "", ...props }: PrimaryButtonProps): JSX.Element { - return ( - - +
-
-

Need Help?

-

+

+

Need Help?

+

If you continue to experience issues, please contact our support team with details about your purchase attempt.

@@ -80,4 +81,4 @@ export default function CheckoutFailedPage(): JSX.Element { ); -} +} \ No newline at end of file diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx index ac0950697..e549bffa2 100644 --- a/src/pages/checkout/index.tsx +++ b/src/pages/checkout/index.tsx @@ -143,7 +143,7 @@ function CheckoutClient(): JSX.Element { paddle.Checkout.open(options); paddle.Spinner.show(); } - }, [query.data, paddle, priceId, quantity, discountCode, submittedEmail, countryCode]); + }, [query.data, paddle, priceId, quantity, discountCode, submittedEmail, countryCode, history, basePath]); return (
@@ -151,13 +151,13 @@ function CheckoutClient(): JSX.Element { {!hasSubmitted ? (
-

Enter your email to continue

+

Enter your email to continue

setUserEmail(e.target.value)} placeholder="your@email.com" - className="w-full px-5 py-3 text-lg border-2 border-gray-300 rounded-lg focus:outline-none focus:border-[--ifm-color-primary] dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:focus:border-[--ifm-color-primary] transition-colors" + className="w-full px-5 py-3 text-lg border-2 border-[var(--color-base-300)] rounded-lg focus:outline-none focus:border-[var(--color-primary)] bg-[var(--color-base-100)] text-[var(--color-base-content)] placeholder:text-[var(--semantic-text-muted)] transition-colors" required /> Continue @@ -165,20 +165,20 @@ function CheckoutClient(): JSX.Element { ) : ( <> {query.isError ? ( -
-

Error loading your orders.

+
+

Error loading your orders.

) : (query.isLoading || !query.data) && (
-
-

Preparing checkout...

+
+

Preparing checkout...

)} {query.data && query.data.orders?.length > 0 && (
-
-

Redirecting to your orders...

+
+

Redirecting to your orders...

)} @@ -195,4 +195,4 @@ export default function CheckoutPage(): JSX.Element { ); -} \ No newline at end of file +} diff --git a/src/pages/checkout/success.tsx b/src/pages/checkout/success.tsx index 656812359..52c0ce8d4 100644 --- a/src/pages/checkout/success.tsx +++ b/src/pages/checkout/success.tsx @@ -2,6 +2,7 @@ import Layout from "@theme/Layout"; import BrowserOnly from "@docusaurus/BrowserOnly"; import { useEffect, useState } from "react"; import { useGateway } from "@site/src/hooks/useGateway"; +import { PrimaryButton } from "@site/src/components/ui/Button"; import type { LicenseResponse, TransactionData } from "@site/src/types"; function SuccessClient(): JSX.Element { @@ -67,51 +68,48 @@ function SuccessClient(): JSX.Element { {isLoading ? ( <>
-

Processing your payment...

-

+

Processing your payment...

+

Please wait while we generate your license.

) : error ? ( <>
⚠️
-

License Generation Failed

-

{error}

-

+

License Generation Failed

+

{error}

+

Your payment was successful, but we encountered an issue generating your license. Please contact support with your transaction ID.

{transactionInfo?.transactionId && ( -
-

Transaction ID

- {transactionInfo.transactionId} +
+

Transaction ID

+ {transactionInfo.transactionId}
)} - + ) : ( <>
-

Payment Successful!

-

+

Payment Successful!

+

Thank you for your purchase. Your order has been processed successfully.

{license && ( -
-

Your License

+
+

Your License

-
-

License Key

- {license.license?.key} +
+

License Key

+ {license.license?.key} @@ -119,41 +117,38 @@ function SuccessClient(): JSX.Element {
- Order ID: - {license.purchase?.id} + Order ID: + {license.purchase?.id}
- Email: - {license.customer?.email} + Email: + {license.customer?.email}
- Plan: - {license.purchase?.planType} + Plan: + {license.purchase?.planType}
- Type: - {license.purchase?.productType} + Type: + {license.purchase?.productType}
- Expires: - + Expires: + {license.license?.expiredAt ? new Date(license.license.expiredAt).toLocaleDateString() : '-'}
-

+

Please save your license key in a safe place.

)} - + )}
@@ -168,4 +163,4 @@ export default function CheckoutSuccessPage(): JSX.Element { ); -} \ No newline at end of file +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 7ecfdce69..ad2e152be 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import Layout from '@theme/Layout'; import { translate } from '@docusaurus/Translate'; import HomepageHeader from '@site/src/components/HomepageHeader'; diff --git a/src/pages/orders.tsx b/src/pages/orders.tsx index d818038df..836758eb8 100644 --- a/src/pages/orders.tsx +++ b/src/pages/orders.tsx @@ -71,13 +71,13 @@ function OrdersClient(): JSX.Element {
{!hasSubmitted ? ( -

Enter your email to view orders

+

Enter your email to view orders

setUserEmail(e.target.value)} placeholder="your@email.com" - className="w-full px-5 py-3 text-lg border-2 border-gray-300 rounded-lg focus:outline-none focus:border-[--ifm-color-primary] dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:focus:border-[--ifm-color-primary] transition-colors" + className="w-full px-5 py-3 text-lg border-2 border-[var(--ifm-color-gray-300)] rounded-lg focus:outline-none focus:border-[var(--ifm-color-primary)] bg-[var(--ifm-background-color)] text-[var(--ifm-font-color-base)] placeholder:text-[var(--ifm-color-gray-600)] transition-colors" required /> View Orders @@ -89,8 +89,8 @@ function OrdersClient(): JSX.Element { <> {query.isError ? (
-
-

Error loading your orders.

+
+

Error loading your orders.

setHasSubmitted(false)}> Try Again @@ -98,20 +98,20 @@ function OrdersClient(): JSX.Element {
) : (query.isLoading || !query.data) && (
-
-

Loading your orders...

+
+

Loading your orders...

)} {query.data && (
-

Your Orders

- {submittedEmail} +

Your Orders

+ {submittedEmail}
{fromCheckout && query.data.orders && query.data.orders.length > 0 && ( -
-

+

+

You already have an order associated with this email.

@@ -119,11 +119,11 @@ function OrdersClient(): JSX.Element { {query.data.orders?.length === 0 ? (
-
-

No orders found for this email.

+
+

No orders found for this email.

- setHasSubmitted(false)} flex-1> + setHasSubmitted(false)} className="flex-1"> Search Another Email @@ -136,33 +136,33 @@ function OrdersClient(): JSX.Element {
{/* Customer Info */} {query.data.customer && ( -
-

Customer

+
+

Customer

- Email - {query.data.customer.email} + Email + {query.data.customer.email}
- Customer ID - {query.data.customer.id} + Customer ID + {query.data.customer.id}
)} {/* License Info */} {query.data.license && ( -
-

License

+
+

License

- License Key - {query.data.license.key} + License Key + {query.data.license.key}
- Status + Status {query.data.license.active ? 'Active' : 'Inactive'} @@ -174,49 +174,49 @@ function OrdersClient(): JSX.Element { {query.data.orders.map((order: Order) => (
-

Order

+

Order

- + Order ID - + {order.id}
- + Amount - + {(order.totalAmount).toFixed(2)} {order.currency}
- + Created - + {new Date(order.createdAt).toLocaleDateString()}
- + Paid At - + {order.paidAt ? new Date(order.paidAt).toLocaleDateString() : '-'}
- + Status {order.status.charAt(0).toUpperCase() + order.status.slice(1)} @@ -227,33 +227,33 @@ function OrdersClient(): JSX.Element { {/* Subscription Info */} {query.data.subscription && ( -
-

Subscription

+
+

Subscription

- Status + Status {query.data.subscription.status.charAt(0).toUpperCase() + query.data.subscription.status.slice(1)}
- Active At - + Active At + {new Date(query.data.subscription.currentPeriodStart).toLocaleDateString()}
- Expires At - + Expires At + {new Date(query.data.subscription.currentPeriodEnd).toLocaleDateString()}
{query.data.subscription.cancelAtPeriodEnd && ( -
-

Will cancel at period end

+
+

Will cancel at period end

)}
@@ -264,7 +264,7 @@ function OrdersClient(): JSX.Element { query.refetch()} disabled={query.isFetching} - className="flex-1 dark:border-0" + className="flex-1" > {query.isFetching ? 'Refreshing...' : 'Refresh'} @@ -293,4 +293,4 @@ export default function OrdersPage(): JSX.Element { ); -} \ No newline at end of file +} From 2895ec543706afc307062ed0c5ab414f1b3f87ca Mon Sep 17 00:00:00 2001 From: icyleaf Date: Wed, 11 Mar 2026 09:24:17 +0800 Subject: [PATCH 11/26] style: re-organize footber links --- docusaurus.config.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 7a8f2b54f..561f80973 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -178,26 +178,27 @@ const config: Config = { label: "Github Discussions", href: "https://github.com/tryzealot/zealot/discussions", }, + { + label: "GitHub", + href: "https://github.com/tryzealot/zealot", + }, ], }, { - title: "More", + title: "Product", items: [ { label: "Demo", href: "https://tryzealot.ews.im", }, - { - label: "GitHub", - href: "https://github.com/tryzealot/zealot", - }, { label: "Changelog", - href: "user-guide/changelog", + href: "docs/user-guide/changelog", }, { + type: "doc", label: "API", - href: "developer-guide/api", + href: "docs/developer-guide/api", }, ], }, From 18d2f1c1741b5ba19d79198018b51d142502a2da Mon Sep 17 00:00:00 2001 From: icyleaf Date: Wed, 11 Mar 2026 10:29:48 +0800 Subject: [PATCH 12/26] feat: add zh-Hans locales --- docusaurus.config.ts | 27 +- i18n/en/code.json | 305 ++++++++++++++++-- i18n/zh-Hans/code.json | 267 ++++++++++++--- .../docusaurus-theme-classic/config.json | 10 - .../docusaurus-theme-classic/footer.json | 16 +- .../docusaurus-theme-classic/navbar.json | 8 - src/components/FeatureSection/index.tsx | 200 ++++++------ .../HomepageHeader/styles.module.css | 6 +- src/pages/checkout/failed.tsx | 84 ----- src/pages/checkout/index.tsx | 27 +- src/pages/checkout/success.tsx | 75 +++-- src/pages/orders.tsx | 148 ++++++--- src/types/docusaurus.d.ts | 16 + 13 files changed, 809 insertions(+), 380 deletions(-) delete mode 100644 i18n/zh-Hans/docusaurus-theme-classic/config.json delete mode 100644 src/pages/checkout/failed.tsx create mode 100644 src/types/docusaurus.d.ts diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 561f80973..ebd4bfee1 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -16,7 +16,7 @@ const isBranchDeploy = const versions = require("./versions.json"); /** @param {string} version */ -function isPrerelease(version) { +function isPrerelease(version: string) { return ( version.includes("alpha") || version.includes("beta") || @@ -193,12 +193,11 @@ const config: Config = { }, { label: "Changelog", - href: "docs/user-guide/changelog", + to: "/docs/user-guide/changelog", }, { - type: "doc", label: "API", - href: "docs/developer-guide/api", + to: "/docs/developer-guide/api", }, ], }, @@ -207,15 +206,15 @@ const config: Config = { items: [ { label: "Terms of Service", - href: "/terms", + to: "/terms", }, { label: "Privacy Policy", - href: "/privacy", + to: "/privacy", }, { label: "EULA", - href: "/eula", + to: "/eula", }, ], }, @@ -249,13 +248,13 @@ const config: Config = { theme: require("prism-react-renderer").themes.github, darkTheme: require("prism-react-renderer").themes.duotoneDark, }, - announcementBar: { - id: 'announcementBar', - backgroundColor: 'var(--ifm-color-primary)', - textColor: 'var(--ifm-heading-color)', - content: `⭐️ If you like Zealot, give it a star on GitHub and vote on ProductHunt`, - isCloseable: true - }, + // announcementBar: { + // id: 'supportUS', + // backgroundColor: 'var(--ifm-color-primary)', + // textColor: 'var(--ifm-heading-color)', + // content: `⭐️ If you like Zealot, give it a star on GitHub and vote on ProductHunt`, + // isCloseable: true + // }, }), plugins: [ diff --git a/i18n/en/code.json b/i18n/en/code.json index b154dbc10..3d58bffb6 100644 --- a/i18n/en/code.json +++ b/i18n/en/code.json @@ -7,31 +7,6 @@ "message": "Continuous everything. Automate the lifecycle of your apps. Connect your CI to build in the cloud, test on thousands of real devices, distribute to beta testers and app stores. All in one place.", "description": "The subtitle description for homepage" }, - "Getting started": { - "message": "Getting started" - }, - "Self-hosted Beta App Distribution for Android, iOS, macOS, Windows and Linux apps.": { - "message": "Self-hosted Beta App Distribution for Android, iOS, macOS, Windows and Linux apps.", - "description": "The homepage title message" - }, - "Easy to Use": { - "message": "Easy to Use" - }, - "Zealot was designed from the ground up to be easily installed and running quickly.": { - "message": "Zealot was designed from the ground up to be easily installed and running quickly." - }, - "Focus on What Matters": { - "message": "Focus on What Matters" - }, - "Zealot lets you focus on your app, and we'll do the chores. Go ahead and upload your apps.": { - "message": "Zealot lets you focus on your app, and we'll do the chores. Go ahead and upload your apps." - }, - "Open Source 100%": { - "message": "Open Source 100%" - }, - "Zealot is an open source project, Everyone could easy self hosted on own servers.": { - "message": "Zealot is an open source project, Everyone could easy self hosted on own servers." - }, "theme.ErrorPageContent.title": { "message": "This page crashed.", "description": "The title of the fallback page when the page crashed" @@ -311,14 +286,6 @@ "message": "Tags", "description": "The title of the tag list page" }, - "theme.unlistedContent.title": { - "message": "Unlisted page", - "description": "The unlisted content banner title" - }, - "theme.unlistedContent.message": { - "message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "description": "The unlisted content banner message" - }, "theme.docs.DocCard.categoryDescription.plurals": { "message": "{count} items", "description": "The default description for a category card in the generated index about how many items this category includes" @@ -446,5 +413,277 @@ "theme.navbar.mobileDropdown.collapseButton.collapseAriaLabel": { "message": "Collapse the dropdown", "description": "The ARIA label of the button to collapse the mobile dropdown navbar item" + }, + "checkout.title": { + "message": "Checkout", + "description": "Checkout page title" + }, + "checkout.loading": { + "message": "Loading checkout...", + "description": "Checkout page loading text" + }, + "checkout.email.title": { + "message": "Enter your email to continue", + "description": "Checkout email form title" + }, + "checkout.email.placeholder": { + "message": "your@email.com", + "description": "Checkout email input placeholder" + }, + "checkout.email.continue": { + "message": "Continue", + "description": "Checkout continue button" + }, + "checkout.error.loadingOrders": { + "message": "Error loading your orders.", + "description": "Error message when loading orders fails" + }, + "checkout.loading.preparing": { + "message": "Preparing checkout...", + "description": "Loading text while preparing checkout" + }, + "checkout.loading.redirecting": { + "message": "Redirecting to your orders...", + "description": "Loading text while redirecting to orders" + }, + "checkout.success.title": { + "message": "Checkout Success", + "description": "Checkout success page title" + }, + "checkout.success.loading": { + "message": "Loading...", + "description": "Checkout success page loading text" + }, + "checkout.success.processing": { + "message": "Processing your payment...", + "description": "Title while processing payment" + }, + "checkout.success.processingDesc": { + "message": "Please wait while we generate your license.", + "description": "Description while processing payment" + }, + "checkout.success.failed.title": { + "message": "License Generation Failed", + "description": "Title when license generation fails" + }, + "checkout.success.failed.desc": { + "message": "Your payment was successful, but we encountered an issue generating your license. Please contact support with your transaction ID.", + "description": "Description when license generation fails" + }, + "checkout.success.failed.tryAgain": { + "message": "Try Again", + "description": "Try again button" + }, + "checkout.success.successTitle": { + "message": "Payment Successful!", + "description": "Title when payment is successful" + }, + "checkout.success.successDesc": { + "message": "Thank you for your purchase. Your order has been processed successfully.", + "description": "Description when payment is successful" + }, + "checkout.success.license.title": { + "message": "Your License", + "description": "License section title" + }, + "checkout.success.license.key": { + "message": "License Key", + "description": "License key label" + }, + "checkout.success.license.copy": { + "message": "Copy", + "description": "Copy button" + }, + "checkout.success.license.copied": { + "message": "Copied!", + "description": "Copied confirmation" + }, + "checkout.success.license.saveReminder": { + "message": "Please save your license key in a safe place.", + "description": "Reminder to save license key" + }, + "checkout.success.order.id": { + "message": "Order ID:", + "description": "Order ID label" + }, + "checkout.success.order.email": { + "message": "Email:", + "description": "Email label" + }, + "checkout.success.order.plan": { + "message": "Plan:", + "description": "Plan label" + }, + "checkout.success.order.type": { + "message": "Type:", + "description": "Type label" + }, + "checkout.success.order.expires": { + "message": "Expires:", + "description": "Expires label" + }, + "checkout.success.returnHome": { + "message": "Return Home", + "description": "Return home button" + }, + "checkout.success.noTransaction": { + "message": "No transaction information found", + "description": "Error when no transaction info found" + }, + "checkout.success.transactionId": { + "message": "Transaction ID", + "description": "Transaction ID label" + }, + "orders.title": { + "message": "My Orders", + "description": "Orders page title" + }, + "orders.loading": { + "message": "Loading...", + "description": "Orders page loading text" + }, + "orders.email.title": { + "message": "Enter your email to view orders", + "description": "Orders email form title" + }, + "orders.email.placeholder": { + "message": "your@email.com", + "description": "Orders email input placeholder" + }, + "orders.email.viewOrders": { + "message": "View Orders", + "description": "View orders button" + }, + "orders.backToPricing": { + "message": "Back to Pricing", + "description": "Back to pricing button" + }, + "orders.error.loading": { + "message": "Error loading your orders.", + "description": "Error message when loading orders fails" + }, + "orders.error.tryAgain": { + "message": "Try Again", + "description": "Try again button" + }, + "orders.loading.text": { + "message": "Loading your orders...", + "description": "Loading orders text" + }, + "orders.sessionError.invalid": { + "message": "Session data is invalid. Please enter your email to view orders.", + "description": "Session data invalid error" + }, + "orders.sessionError.config": { + "message": "Session configuration error. Please enter your email to view orders.", + "description": "Session configuration error" + }, + "orders.yourOrders.title": { + "message": "Your Orders", + "description": "Your orders section title" + }, + "orders.existingOrder.warning": { + "message": "You already have an order associated with this email.", + "description": "Warning when user has existing order" + }, + "orders.noOrders": { + "message": "No orders found for this email.", + "description": "Message when no orders found" + }, + "orders.searchAnother": { + "message": "Search Another Email", + "description": "Search another email button" + }, + "orders.customer.title": { + "message": "Customer", + "description": "Customer section title" + }, + "orders.customer.email": { + "message": "Email", + "description": "Customer email label" + }, + "orders.customer.id": { + "message": "Customer ID", + "description": "Customer ID label" + }, + "orders.license.title": { + "message": "License", + "description": "License section title" + }, + "orders.license.key": { + "message": "License Key", + "description": "License key label" + }, + "orders.license.status": { + "message": "Status", + "description": "License status label" + }, + "orders.license.status.active": { + "message": "Active", + "description": "Active status" + }, + "orders.license.status.inactive": { + "message": "Inactive", + "description": "Inactive status" + }, + "orders.order.title": { + "message": "Order", + "description": "Order section title" + }, + "orders.order.id": { + "message": "Order ID", + "description": "Order ID label" + }, + "orders.order.amount": { + "message": "Amount", + "description": "Order amount label" + }, + "orders.order.created": { + "message": "Created", + "description": "Order created date label" + }, + "orders.order.paidAt": { + "message": "Paid At", + "description": "Order paid date label" + }, + "orders.order.status": { + "message": "Status", + "description": "Order status label" + }, + "orders.order.status.completed": { + "message": "Completed", + "description": "Completed status" + }, + "orders.order.status.pending": { + "message": "Pending", + "description": "Pending status" + }, + "orders.subscription.title": { + "message": "Subscription", + "description": "Subscription section title" + }, + "orders.subscription.status": { + "message": "Status", + "description": "Subscription status label" + }, + "orders.subscription.activeAt": { + "message": "Active At", + "description": "Subscription active date label" + }, + "orders.subscription.expiresAt": { + "message": "Expires At", + "description": "Subscription expires date label" + }, + "orders.subscription.cancelWarning": { + "message": "Will cancel at period end", + "description": "Cancel at period end warning" + }, + "orders.refresh": { + "message": "Refresh", + "description": "Refresh button" + }, + "orders.refreshing": { + "message": "Refreshing...", + "description": "Refreshing button text" } } diff --git a/i18n/zh-Hans/code.json b/i18n/zh-Hans/code.json index 4264071f6..7ebe086dc 100644 --- a/i18n/zh-Hans/code.json +++ b/i18n/zh-Hans/code.json @@ -278,14 +278,6 @@ "message": "折叠侧边栏分类 '{label}'", "description": "The ARIA label to collapse the sidebar category" }, - "theme.unlistedContent.title": { - "message": "未列出页", - "description": "The unlisted content banner title" - }, - "theme.unlistedContent.message": { - "message": "此页面未列出。搜索引擎不会对其索引,只有拥有直接链接的用户才能访问。", - "description": "The unlisted content banner message" - }, "theme.blog.author.pageTitle": { "message": "{authorName} - {nPosts}", "description": "The title of the page for a blog author" @@ -326,27 +318,6 @@ "message": "折叠下拉菜单", "description": "The ARIA label of the button to collapse the mobile dropdown navbar item" }, - "Easy to Use": { - "message": "轻松使用" - }, - "Focus on What Matters": { - "message": "专注于重要的事情" - }, - "Open Source 100%": { - "message": "100% 开源项目" - }, - "Zealot was designed from the ground up to be easily installed and running quickly.": { - "message": "Zealot 提供简单便捷的部署方式实现一键安装和运行。" - }, - "Zealot lets you focus on your app, and we'll do the chores. Go ahead and upload your apps.": { - "message": "Zealot 使得开发者可以关注于应用开发本身,我们来处理持续交付和测试的工作,立即上传应用吧。" - }, - "Zealot is an open source project, Everyone could easy self hosted on own servers.": { - "message": "Zealot 是一个开源项目,每个人都可以私有化部署在自己的服务器。" - }, - "Getting started": { - "message": "快速上手" - }, "homepage.header.title": { "message": "Zealot", "description": "The main title for homepage" @@ -355,10 +326,6 @@ "message": "开源自部署持续集成一切跟应用有关事情,接入任意 CI 系统一切自动化处理,企业多年实战经验,独立部署提供企业打包分发流程、上传应用全套流程方案", "description": "The subtitle description for homepage" }, - "Self-hosted Beta App Distribution for Android, iOS, macOS, Windows and Linux apps.": { - "message": "私有化部署 Android、iOS、macOS、Windows 和 Linux 应用托管服务", - "description": "The homepage title message" - }, "homepage.button.getStarted": { "message": "快速开始", "description": "Get started button text" @@ -385,51 +352,51 @@ }, "homepage.feature.deployment.title": { "message": "快捷部署", - "description": "Easy deployment feature title" + "description": "Easy Deployment feature title" }, "homepage.feature.deployment.description": { "message": "支持 Docker、Kubernetes、Nomad 及云平台服务快速部署属于您的私有化应用分发平台。", - "description": "Easy deployment feature description" + "description": "Easy Deployment feature description" }, "homepage.feature.multiPlatform.title": { "message": "多平台应用托管", - "description": "Multi-platform feature title" + "description": "Multi-Platform feature title" }, "homepage.feature.multiPlatform.description": { "message": "支持 Android、iOS、macOS、Windows 和 Linux 应用,一个平台满足所有需求。", - "description": "Multi-platform feature description" + "description": "Multi-Platform feature description" }, "homepage.feature.cicd.title": { "message": "CI/CD 持续集成", - "description": "CI/CD integration feature title" + "description": "CI/CD Integration feature title" }, "homepage.feature.cicd.description": { "message": "无缝集成您现有的 CI/CD 流水线,实现应用自动化分发。", - "description": "CI/CD integration feature description" + "description": "CI/CD Integration feature description" }, "homepage.feature.teamManagement.title": { "message": "团队管理", - "description": "Team management feature title" + "description": "Team Management feature title" }, "homepage.feature.teamManagement.description": { "message": "管理团队成员并控制访问权限。通过频道组织应用,有效管理用户角色。", - "description": "Team management feature description" + "description": "Team Management feature description" }, "homepage.feature.developerFriendly.title": { "message": "开发者友好", - "description": "Developer friendly feature title" + "description": "Developer Friendly feature title" }, "homepage.feature.developerFriendly.description": { "message": "提供丰富的 SDK 和 RESTful API,支持多种编程语言和平台的无缝集成。", - "description": "Developer friendly feature description" + "description": "Developer Friendly feature description" }, "homepage.feature.security.title": { "message": "安全私有", - "description": "Security feature title" + "description": "Secure & Private feature title" }, "homepage.feature.security.description": { "message": "企业级安全和隐私控制,确保您的应用和数据安全。", - "description": "Security feature description" + "description": "Secure & Private feature description" }, "homepage.seo.title": { "message": "私有化部署 Android、iOS、macOS、Windows 和 Linux 应用托管服务" @@ -526,12 +493,6 @@ "pricing.plan.free.price": { "message": "免费" }, - "pricing.plan.pro.price": { - "message": "¥3499 / 年" - }, - "pricing.plan.pro.payment_integration_coming_soon": { - "message": "支付集成即将推出!" - }, "donate.title": { "message": "支持我们" }, @@ -561,5 +522,209 @@ }, "donate.thankYou": { "message": "感谢您选择 Zealot 作为您的应用分发解决方案。" + }, + "checkout.title": { + "message": "结账" + }, + "checkout.loading": { + "message": "正在加载结账页面..." + }, + "checkout.email.title": { + "message": "请输入您的邮箱继续" + }, + "checkout.email.placeholder": { + "message": "your@email.com" + }, + "checkout.email.continue": { + "message": "继续" + }, + "checkout.error.loadingOrders": { + "message": "加载订单时出错。" + }, + "checkout.loading.preparing": { + "message": "正在准备结账..." + }, + "checkout.loading.redirecting": { + "message": "正在跳转到您的订单..." + }, + "checkout.success.title": { + "message": "支付成功" + }, + "checkout.success.loading": { + "message": "加载中..." + }, + "checkout.success.processing": { + "message": "正在处理您的支付..." + }, + "checkout.success.processingDesc": { + "message": "请稍候,我们正在生成您的许可证。" + }, + "checkout.success.failed.title": { + "message": "许可证生成失败" + }, + "checkout.success.failed.desc": { + "message": "您的支付已成功,但我们在生成许可证时遇到问题。请联系客服并提供您的交易 ID。" + }, + "checkout.success.failed.tryAgain": { + "message": "重试" + }, + "checkout.success.successTitle": { + "message": "支付成功!" + }, + "checkout.success.successDesc": { + "message": "感谢您的购买。您的订单已成功处理。" + }, + "checkout.success.license.title": { + "message": "您的许可证" + }, + "checkout.success.license.key": { + "message": "许可证密钥" + }, + "checkout.success.license.copy": { + "message": "复制" + }, + "checkout.success.license.copied": { + "message": "已复制!" + }, + "checkout.success.license.saveReminder": { + "message": "请妥善保存您的许可证密钥。" + }, + "checkout.success.order.id": { + "message": "订单号:" + }, + "checkout.success.order.email": { + "message": "邮箱:" + }, + "checkout.success.order.plan": { + "message": "套餐:" + }, + "checkout.success.order.type": { + "message": "类型:" + }, + "checkout.success.order.expires": { + "message": "到期时间:" + }, + "checkout.success.returnHome": { + "message": "返回首页" + }, + "checkout.success.noTransaction": { + "message": "未找到交易信息" + }, + "checkout.success.transactionId": { + "message": "交易 ID" + }, + "orders.title": { + "message": "我的订单" + }, + "orders.loading": { + "message": "加载中..." + }, + "orders.email.title": { + "message": "请输入您的邮箱查看订单" + }, + "orders.email.placeholder": { + "message": "your@email.com" + }, + "orders.email.viewOrders": { + "message": "查看订单" + }, + "orders.backToPricing": { + "message": "返回价格页" + }, + "orders.error.loading": { + "message": "加载订单时出错。" + }, + "orders.error.tryAgain": { + "message": "重试" + }, + "orders.loading.text": { + "message": "正在加载您的订单..." + }, + "orders.sessionError.invalid": { + "message": "会话数据无效。请输入您的邮箱查看订单。" + }, + "orders.sessionError.config": { + "message": "会话配置错误。请输入您的邮箱查看订单。" + }, + "orders.yourOrders.title": { + "message": "您的订单" + }, + "orders.existingOrder.warning": { + "message": "此邮箱已有关联订单。" + }, + "orders.noOrders": { + "message": "未找到此邮箱的订单。" + }, + "orders.searchAnother": { + "message": "搜索其他邮箱" + }, + "orders.customer.title": { + "message": "客户信息" + }, + "orders.customer.email": { + "message": "邮箱" + }, + "orders.customer.id": { + "message": "客户 ID" + }, + "orders.license.title": { + "message": "许可证" + }, + "orders.license.key": { + "message": "许可证密钥" + }, + "orders.license.status": { + "message": "状态" + }, + "orders.license.status.active": { + "message": "有效" + }, + "orders.license.status.inactive": { + "message": "无效" + }, + "orders.order.title": { + "message": "订单" + }, + "orders.order.id": { + "message": "订单号" + }, + "orders.order.amount": { + "message": "金额" + }, + "orders.order.created": { + "message": "创建时间" + }, + "orders.order.paidAt": { + "message": "支付时间" + }, + "orders.order.status": { + "message": "状态" + }, + "orders.order.status.completed": { + "message": "已完成" + }, + "orders.order.status.pending": { + "message": "待处理" + }, + "orders.subscription.title": { + "message": "订阅" + }, + "orders.subscription.status": { + "message": "状态" + }, + "orders.subscription.activeAt": { + "message": "生效时间" + }, + "orders.subscription.expiresAt": { + "message": "到期时间" + }, + "orders.subscription.cancelWarning": { + "message": "将在周期结束时取消" + }, + "orders.refresh": { + "message": "刷新" + }, + "orders.refreshing": { + "message": "刷新中..." } } diff --git a/i18n/zh-Hans/docusaurus-theme-classic/config.json b/i18n/zh-Hans/docusaurus-theme-classic/config.json deleted file mode 100644 index b36af288a..000000000 --- a/i18n/zh-Hans/docusaurus-theme-classic/config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "theme.announcementBar.content": { - "message": "Nous cherchons à réorganiser notre documentation, veuillez remplir ce sondage", - "description": "The content of the announcement bar" - }, - "theme.announcementBar.closeButton": { - "message": "关闭", - "description": "The announcement bar close button aria label" - } -} \ No newline at end of file diff --git a/i18n/zh-Hans/docusaurus-theme-classic/footer.json b/i18n/zh-Hans/docusaurus-theme-classic/footer.json index ad3c522be..5178288ae 100644 --- a/i18n/zh-Hans/docusaurus-theme-classic/footer.json +++ b/i18n/zh-Hans/docusaurus-theme-classic/footer.json @@ -3,10 +3,6 @@ "message": "社群", "description": "The title of the footer links column with title=Community in the footer" }, - "link.title.More": { - "message": "更多资源", - "description": "The title of the footer links column with title=More in the footer" - }, "link.item.label.Telegram": { "message": "电报", "description": "The label of footer link with label=Telegram linking to https://t.me/+csa3Y2KOx44wMGRl" @@ -42,5 +38,17 @@ "copyright": { "message": "Copyright © 2018 - 2026 Zealot.", "description": "The footer copyright" + }, + "link.title.Product": { + "message": "产品服务", + "description": "The title of the footer links column with title=Product in the footer" + }, + "link.item.label.Changelog": { + "message": "变更日志", + "description": "The label of footer link with label=Changelog linking to /docs/user-guide/changelog" + }, + "link.item.label.API": { + "message": "接口文档", + "description": "The label of footer link with label=API linking to /docs/developer-guide/api" } } diff --git a/i18n/zh-Hans/docusaurus-theme-classic/navbar.json b/i18n/zh-Hans/docusaurus-theme-classic/navbar.json index 942a90713..2252d2663 100644 --- a/i18n/zh-Hans/docusaurus-theme-classic/navbar.json +++ b/i18n/zh-Hans/docusaurus-theme-classic/navbar.json @@ -19,14 +19,6 @@ "message": "Zealot Logo", "description": "The alt text of navbar logo" }, - "item.label.API": { - "message": "API", - "description": "Navbar item with label API" - }, - "item.label.Changelog": { - "message": "变更日志", - "description": "Navbar item with label Changelog" - }, "item.label.Donate": { "message": "捐献", "description": "Navbar item with label Donate" diff --git a/src/components/FeatureSection/index.tsx b/src/components/FeatureSection/index.tsx index 6618f0de2..e0214c51f 100644 --- a/src/components/FeatureSection/index.tsx +++ b/src/components/FeatureSection/index.tsx @@ -2,104 +2,118 @@ import React from 'react'; import Translate from '@docusaurus/Translate'; import styles from './styles.module.css'; -interface FeatureConfig { - icon: string; - titleId: string; - titleDefault: string; - descriptionId: string; - descriptionDefault: string; -} - -const FEATURES_CONFIG: FeatureConfig[] = [ - { - icon: '🚀', - titleId: 'homepage.feature.deployment.title', - titleDefault: 'Easy Deployment', - descriptionId: 'homepage.feature.deployment.description', - descriptionDefault: 'Deploy with Docker in minutes. Self-hosted solution with complete control over your data.', - }, - { - icon: '📱', - titleId: 'homepage.feature.multiPlatform.title', - titleDefault: 'Multi-Platform', - descriptionId: 'homepage.feature.multiPlatform.description', - descriptionDefault: 'Support for Android, iOS, macOS, Windows, and Linux apps. One solution for all platforms.', - }, - { - icon: '🔄', - titleId: 'homepage.feature.cicd.title', - titleDefault: 'CI/CD Integration', - descriptionId: 'homepage.feature.cicd.description', - descriptionDefault: 'Seamlessly integrate with your existing CI/CD pipeline for automated app distribution.', - }, - { - icon: '👥', - titleId: 'homepage.feature.teamManagement.title', - titleDefault: 'Team Management', - descriptionId: 'homepage.feature.teamManagement.description', - descriptionDefault: 'Manage team members and control access permissions. Organize apps by channels and manage user roles effectively.', - }, - { - icon: '⚡', - titleId: 'homepage.feature.developerFriendly.title', - titleDefault: 'Developer Friendly', - descriptionId: 'homepage.feature.developerFriendly.description', - descriptionDefault: 'Rich SDKs and RESTful APIs for seamless integration. Support for multiple programming languages and platforms.', - }, - { - icon: '🔒', - titleId: 'homepage.feature.security.title', - titleDefault: 'Secure & Private', - descriptionId: 'homepage.feature.security.description', - descriptionDefault: 'Keep your apps and data secure with enterprise-grade security and privacy controls.', - }, -]; - -interface FeatureProps { - icon: string; - titleId: string; - titleDefault: string; - descriptionId: string; - descriptionDefault: string; -} - -function Feature({ icon, titleId, titleDefault, descriptionId, descriptionDefault }: FeatureProps): JSX.Element { - return ( -
-
{icon}
-

- - {titleDefault} - -

-

- - {descriptionDefault} - -

-
- ); -} - export default function FeatureSection(): JSX.Element { return (
- {FEATURES_CONFIG.map((feature, index) => ( - - ))} +
+
🚀
+

+ + Easy Deployment + +

+

+ + Deploy with Docker in minutes. Self-hosted solution with complete control over your data. + +

+
+ +
+
📱
+

+ + Multi-Platform + +

+

+ + Support for Android, iOS, macOS, Windows, and Linux apps. One solution for all platforms. + +

+
+ +
+
🔄
+

+ + CI/CD Integration + +

+

+ + Seamlessly integrate with your existing CI/CD pipeline for automated app distribution. + +

+
+ +
+
👥
+

+ + Team Management + +

+

+ + Manage team members and control access permissions. Organize apps by channels and manage user roles effectively. + +

+
+ +
+
+

+ + Developer Friendly + +

+

+ + Rich SDKs and RESTful APIs for seamless integration. Support for multiple programming languages and platforms. + +

+
+ +
+
🔒
+

+ + Secure & Private + +

+

+ + Keep your apps and data secure with enterprise-grade security and privacy controls. + +

+
diff --git a/src/components/HomepageHeader/styles.module.css b/src/components/HomepageHeader/styles.module.css index 5fbd95ae7..1575c75ea 100644 --- a/src/components/HomepageHeader/styles.module.css +++ b/src/components/HomepageHeader/styles.module.css @@ -104,7 +104,7 @@ position: relative; width: 100%; max-width: 400px; - border-radius: 16px; + border-radius: 15px; overflow: hidden; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); background: rgba(255, 255, 255, 0.1); @@ -112,6 +112,10 @@ border: 1px solid rgba(255, 255, 255, 0.2); } +.showcaseImage img { + display: block !important; +} + [data-theme='dark'] .showcaseImage { box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4); background: rgba(0, 0, 0, 0.2); diff --git a/src/pages/checkout/failed.tsx b/src/pages/checkout/failed.tsx deleted file mode 100644 index 6a7591828..000000000 --- a/src/pages/checkout/failed.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import Layout from '@theme/Layout'; -import BrowserOnly from '@docusaurus/BrowserOnly'; -import { useHistory } from '@docusaurus/router'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import { PrimaryButton, OutlineButton } from '@site/src/components/ui/Button'; - -interface ErrorInfo { - errorCode?: string; - errorMessage?: string; -} - -function FailedClient(): JSX.Element { - const history = useHistory(); - const { i18n } = useDocusaurusContext(); - const [errorInfo, setErrorInfo] = useState(null); - - useEffect(() => { - // 从 sessionStorage 读取错误信息 - const storedData = sessionStorage.getItem('paddleError'); - if (storedData) { - const errorData = JSON.parse(storedData) as ErrorInfo; - setErrorInfo(errorData); - console.log('Payment failed:', errorData); - // 清除 sessionStorage 中的数据 - sessionStorage.removeItem('paddleError'); - } else { - setErrorInfo({ errorCode: 'unknown', errorMessage: 'Payment failed' }); - } - }, []); - - const basePath = i18n.currentLocale !== 'en' ? `/${i18n.currentLocale}` : ''; - - return ( -
-
-

Payment Failed

-

- We're sorry, but your payment could not be processed. -

- - {errorInfo?.errorMessage && ( -
-

- Error: {errorInfo.errorMessage} -

-
- )} - -
- history.goBack()} - fullWidth={false} - > - Try Again - - window.location.href = `${basePath}/pricing`} - fullWidth={false} - > - Return to Pricing - -
- -
-

Need Help?

-

- If you continue to experience issues, please contact our support team - with details about your purchase attempt. -

-
-
- ); -} - -export default function CheckoutFailedPage(): JSX.Element { - return ( - - Loading...
}> - {() => } - - - ); -} \ No newline at end of file diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx index e549bffa2..fffdd32cf 100644 --- a/src/pages/checkout/index.tsx +++ b/src/pages/checkout/index.tsx @@ -3,6 +3,7 @@ import Layout from "@theme/Layout"; import BrowserOnly from "@docusaurus/BrowserOnly"; import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; import { useHistory } from "@docusaurus/router"; +import Translate, { translate } from "@docusaurus/Translate"; import { usePaddleClient } from "@site/src/hooks/usePaddlePrices"; import { useGateway } from "@site/src/hooks/useGateway"; import { getCountryCodeFromLocale } from "@site/src/lib/utils"; @@ -151,34 +152,44 @@ function CheckoutClient(): JSX.Element { {!hasSubmitted ? ( -

Enter your email to continue

+

+ Enter your email to continue +

setUserEmail(e.target.value)} - placeholder="your@email.com" + placeholder={translate({ id: "checkout.email.placeholder", message: "your@email.com" })} className="w-full px-5 py-3 text-lg border-2 border-[var(--color-base-300)] rounded-lg focus:outline-none focus:border-[var(--color-primary)] bg-[var(--color-base-100)] text-[var(--color-base-content)] placeholder:text-[var(--semantic-text-muted)] transition-colors" required /> - Continue + + Continue + ) : ( <> {query.isError ? (
-

Error loading your orders.

+

+ Error loading your orders. +

) : (query.isLoading || !query.data) && (
-

Preparing checkout...

+

+ Preparing checkout... +

)} {query.data && query.data.orders?.length > 0 && (
-

Redirecting to your orders...

+

+ Redirecting to your orders... +

)} @@ -189,8 +200,8 @@ function CheckoutClient(): JSX.Element { export default function CheckoutPage(): JSX.Element { return ( - - Loading checkout...
}> + + {translate({ id: "checkout.loading", message: "Loading checkout..." })}
}> {() => } diff --git a/src/pages/checkout/success.tsx b/src/pages/checkout/success.tsx index 52c0ce8d4..aa2ccad27 100644 --- a/src/pages/checkout/success.tsx +++ b/src/pages/checkout/success.tsx @@ -3,6 +3,7 @@ import BrowserOnly from "@docusaurus/BrowserOnly"; import { useEffect, useState } from "react"; import { useGateway } from "@site/src/hooks/useGateway"; import { PrimaryButton } from "@site/src/components/ui/Button"; +import Translate, { translate } from "@docusaurus/Translate"; import type { LicenseResponse, TransactionData } from "@site/src/types"; function SuccessClient(): JSX.Element { @@ -29,7 +30,7 @@ function SuccessClient(): JSX.Element { const transactionData: TransactionData = JSON.parse(storedData); setTransactionInfo(transactionData); } else { - setError("No transaction information found"); + setError(translate({ id: "checkout.success.noTransaction", message: "No transaction information found" })); setIsLoading(false); } }, []); @@ -68,72 +69,100 @@ function SuccessClient(): JSX.Element { {isLoading ? ( <>
-

Processing your payment...

+

+ Processing your payment... +

- Please wait while we generate your license. + Please wait while we generate your license.

) : error ? ( <>
⚠️
-

License Generation Failed

+

+ License Generation Failed +

{error}

- Your payment was successful, but we encountered an issue generating your license. - Please contact support with your transaction ID. + + Your payment was successful, but we encountered an issue generating your license. + Please contact support with your transaction ID. +

{transactionInfo?.transactionId && (
-

Transaction ID

+

+ Transaction ID +

{transactionInfo.transactionId}
)} window.location.reload()}> - Try Again + Try Again ) : ( <>
-

Payment Successful!

+

+ Payment Successful! +

- Thank you for your purchase. Your order has been processed successfully. + + Thank you for your purchase. Your order has been processed successfully. +

{license && (
-

Your License

+

+ Your License +

-

License Key

+

+ License Key +

{license.license?.key}
- Order ID: + + Order ID: + {license.purchase?.id}
- Email: + + Email: + {license.customer?.email}
- Plan: + + Plan: + {license.purchase?.planType}
- Type: + + Type: + {license.purchase?.productType}
- Expires: + + Expires: + {license.license?.expiredAt ? new Date(license.license.expiredAt).toLocaleDateString() : '-'} @@ -141,13 +170,15 @@ function SuccessClient(): JSX.Element {

- Please save your license key in a safe place. + + Please save your license key in a safe place. +

)} window.location.href = "/"}> - Return Home + Return Home )} @@ -157,8 +188,8 @@ function SuccessClient(): JSX.Element { export default function CheckoutSuccessPage(): JSX.Element { return ( - - Loading...
}> + + {translate({ id: "checkout.success.loading", message: "Loading..." })}
}> {() => } diff --git a/src/pages/orders.tsx b/src/pages/orders.tsx index 836758eb8..c6a6eee6f 100644 --- a/src/pages/orders.tsx +++ b/src/pages/orders.tsx @@ -6,8 +6,9 @@ import { useHistory } from "@docusaurus/router"; import { useGateway } from "@site/src/hooks/useGateway"; import { useCustomerOrders } from "@site/src/lib/query"; import { decrypt } from "@site/src/lib/crypto"; -import { PrimaryButton, SecondaryButton } from "@site/src/components/ui/Button"; -import type { Order, License, Subscription, Customer } from "@site/src/types"; +import { OutlineButton, PrimaryButton, SecondaryButton } from "@site/src/components/ui/Button"; +import Translate, { translate } from "@docusaurus/Translate"; +import type { Order } from "@site/src/types"; function OrdersClient(): JSX.Element { const { i18n } = useDocusaurusContext(); @@ -17,6 +18,7 @@ function OrdersClient(): JSX.Element { const [submittedEmail, setSubmittedEmail] = useState(""); const [hasSubmitted, setHasSubmitted] = useState(false); const [fromCheckout, setFromCheckout] = useState(false); + const [sessionError, setSessionError] = useState(null); // 从 sessionStorage 读取预填充的 email(从 checkout 页面跳转过来) useEffect(() => { @@ -33,19 +35,14 @@ function OrdersClient(): JSX.Element { sessionStorage.removeItem("ordersEmail"); }) .catch(() => { - // 解密失败,可能是未加密的旧数据 - setUserEmail(storedEmail); - setSubmittedEmail(storedEmail); - setHasSubmitted(true); - setFromCheckout(true); + // 解密失败,清除数据并显示错误提示 sessionStorage.removeItem("ordersEmail"); + setSessionError(translate({ id: "orders.sessionError.invalid", message: "Session data is invalid. Please enter your email to view orders." })); }); } else { - setUserEmail(storedEmail); - setSubmittedEmail(storedEmail); - setHasSubmitted(true); - setFromCheckout(true); + // 没有加密密钥,无法解密,清除数据 sessionStorage.removeItem("ordersEmail"); + setSessionError(translate({ id: "orders.sessionError.config", message: "Session configuration error. Please enter your email to view orders." })); } } }, []); @@ -71,48 +68,63 @@ function OrdersClient(): JSX.Element {
{!hasSubmitted ? (
-

Enter your email to view orders

+

+ Enter your email to view orders +

+ {sessionError && ( +
+

{sessionError}

+
+ )} setUserEmail(e.target.value)} - placeholder="your@email.com" + placeholder={translate({ id: "orders.email.placeholder", message: "your@email.com" })} className="w-full px-5 py-3 text-lg border-2 border-[var(--ifm-color-gray-300)] rounded-lg focus:outline-none focus:border-[var(--ifm-color-primary)] bg-[var(--ifm-background-color)] text-[var(--ifm-font-color-base)] placeholder:text-[var(--ifm-color-gray-600)] transition-colors" required /> - View Orders - - Back to Pricing - + + View Orders + + + Back to Pricing +
) : ( <> {query.isError ? (
-

Error loading your orders.

+

+ Error loading your orders. +

setHasSubmitted(false)}> - Try Again + Try Again
) : (query.isLoading || !query.data) && (
-

Loading your orders...

+

+ Loading your orders... +

)} {query.data && (
-

Your Orders

+

+ Your Orders +

{submittedEmail}
{fromCheckout && query.data.orders && query.data.orders.length > 0 && (

- You already have an order associated with this email. + You already have an order associated with this email.

)} @@ -120,15 +132,17 @@ function OrdersClient(): JSX.Element { {query.data.orders?.length === 0 ? (
-

No orders found for this email.

+

+ No orders found for this email. +

+ + Back to Pricing + setHasSubmitted(false)} className="flex-1"> - Search Another Email + Search Another Email - - Back to Pricing -
) : ( @@ -137,13 +151,19 @@ function OrdersClient(): JSX.Element { {/* Customer Info */} {query.data.customer && (
-

Customer

+

+ Customer +

- Email + + Email + {query.data.customer.email}
- Customer ID + + Customer ID + {query.data.customer.id}
@@ -152,19 +172,27 @@ function OrdersClient(): JSX.Element { {/* License Info */} {query.data.license && (
-

License

+

+ License +

- License Key + + License Key + {query.data.license.key}
- Status + + Status + - {query.data.license.active ? 'Active' : 'Inactive'} + {query.data.license.active + ? translate({ id: "orders.license.status.active", message: "Active" }) + : translate({ id: "orders.license.status.inactive", message: "Inactive" })}
@@ -176,10 +204,12 @@ function OrdersClient(): JSX.Element { key={order.id} className="px-6 py-5 bg-[var(--ifm-background-color)] border-2 border-[var(--ifm-color-gray-300)] rounded-xl shadow-sm hover:shadow-md transition-shadow dark:bg-[var(--ifm-background-surface-color)]" > -

Order

+

+ Order +

- Order ID + Order ID {order.id} @@ -187,7 +217,7 @@ function OrdersClient(): JSX.Element {
- Amount + Amount {(order.totalAmount).toFixed(2)} {order.currency} @@ -195,7 +225,7 @@ function OrdersClient(): JSX.Element {
- Created + Created {new Date(order.createdAt).toLocaleDateString()} @@ -203,7 +233,7 @@ function OrdersClient(): JSX.Element {
- Paid At + Paid At {order.paidAt ? new Date(order.paidAt).toLocaleDateString() : '-'} @@ -211,14 +241,16 @@ function OrdersClient(): JSX.Element {
- Status + Status - {order.status.charAt(0).toUpperCase() + order.status.slice(1)} + {order.status === 'completed' + ? translate({ id: "orders.order.status.completed", message: "Completed" }) + : translate({ id: "orders.order.status.pending", message: "Pending" })}
@@ -228,9 +260,13 @@ function OrdersClient(): JSX.Element { {/* Subscription Info */} {query.data.subscription && (
-

Subscription

+

+ Subscription +

- Status + + Status +
- Active At + + Active At + {new Date(query.data.subscription.currentPeriodStart).toLocaleDateString()}
- Expires At + + Expires At + {new Date(query.data.subscription.currentPeriodEnd).toLocaleDateString()}
{query.data.subscription.cancelAtPeriodEnd && (
-

Will cancel at period end

+

+ Will cancel at period end +

)}
@@ -261,18 +303,20 @@ function OrdersClient(): JSX.Element {
- query.refetch()} disabled={query.isFetching} className="flex-1" > - {query.isFetching ? 'Refreshing...' : 'Refresh'} - + {query.isFetching + ? translate({ id: "orders.refreshing", message: "Refreshing..." }) + : translate({ id: "orders.refresh", message: "Refresh" })} + setHasSubmitted(false)} className="flex-1" > - Search Another Email + Search Another Email
@@ -287,8 +331,8 @@ function OrdersClient(): JSX.Element { export default function OrdersPage(): JSX.Element { return ( - - Loading...
}> + + {translate({ id: "orders.loading", message: "Loading..." })}
}> {() => } diff --git a/src/types/docusaurus.d.ts b/src/types/docusaurus.d.ts new file mode 100644 index 000000000..1b0ce9ab5 --- /dev/null +++ b/src/types/docusaurus.d.ts @@ -0,0 +1,16 @@ +/// + +// 修复 React 19 中 Layout 组件的类型问题 +declare module '@theme/Layout' { + import type { ReactElement, ReactNode } from 'react'; + + export interface Props { + readonly children?: ReactNode; + readonly noFooter?: boolean; + readonly wrapperClassName?: string; + readonly title?: string; + readonly description?: string; + } + + export default function Layout(props: Props): ReactElement; +} From f5da530097214ed9086596951c5e8958bc227080 Mon Sep 17 00:00:00 2001 From: icyleaf Date: Wed, 11 Mar 2026 10:34:37 +0800 Subject: [PATCH 13/26] chore: update pnpm lock --- pnpm-lock.yaml | 182 ++++++++++++++++++++++++------------------------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c96fec850..85b18f63f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,10 +10,10 @@ importers: dependencies: '@docusaurus/core': specifier: ^3.9.2 - version: 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + version: 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) '@docusaurus/preset-classic': specifier: ^3.9.2 - version: 3.9.2(@algolia/client-search@5.40.0)(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(@types/react@19.2.9)(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.8.2) + version: 3.9.2(@algolia/client-search@5.40.0)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(@types/react@19.2.14)(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.8.2) '@docusaurus/tsconfig': specifier: ^3.9.2 version: 3.9.2 @@ -22,7 +22,7 @@ importers: version: 1.0.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mdx-js/react': specifier: ^3.1.1 - version: 3.1.1(@types/react@19.2.9)(react@19.0.0) + version: 3.1.1(@types/react@19.2.14)(react@19.0.0) '@paddle/paddle-js': specifier: ^1.6.1 version: 1.6.1 @@ -49,14 +49,14 @@ importers: version: 19.0.0(react@19.0.0) redocusaurus: specifier: ^2.5.0 - version: 2.5.0(@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@docusaurus/utils@3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(core-js@3.42.0)(mobx@6.13.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(styled-components@6.1.18(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(webpack@5.99.8) + version: 2.5.0(@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@docusaurus/utils@3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(core-js@3.42.0)(mobx@6.13.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(styled-components@6.1.18(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(webpack@5.99.8) devDependencies: '@babel/helper-explode-assignable-expression': specifier: ^7.18.6 version: 7.18.6 '@crowdin/cli': specifier: ^4.13.0 - version: 4.13.0 + version: 4.14.0 '@docusaurus/module-type-aliases': specifier: ^3.9.2 version: 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -742,8 +742,8 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - '@crowdin/cli@4.13.0': - resolution: {integrity: sha512-2EJN0VCTVMpSjF9hTsOau9gYT2cMRvQUXH+gQP67Ha85s5f68gkjFzC2gEGNoeDtRWdw+b9MlUNkTlELmpZBcQ==} + '@crowdin/cli@4.14.0': + resolution: {integrity: sha512-sN3puFcyPWbG9kffuOBqkrcNv/retP6ai12DcY6Qz934JUVaz4xQOipz6XsxKYCctgJQRkyGDDSgwh+vYD897A==} hasBin: true '@csstools/cascade-layer-name-parser@2.0.5': @@ -1765,8 +1765,8 @@ packages: '@types/react@19.1.12': resolution: {integrity: sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==} - '@types/react@19.2.9': - resolution: {integrity: sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==} + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} '@types/retry@0.12.2': resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} @@ -3114,7 +3114,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} @@ -4052,8 +4052,8 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} minizlib@3.1.0: @@ -5492,8 +5492,8 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - tar@7.5.4: - resolution: {integrity: sha512-AN04xbWGrSTDmVwlI4/GTlIIwMFk/XEv7uL8aa57zuvRy6s4hdBed+lVq2fAZ89XDa7Us3ANXcE3Tvqvja1kTA==} + tar@7.5.9: + resolution: {integrity: sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==} engines: {node: '>=18'} terser-webpack-plugin@5.3.14: @@ -6783,12 +6783,12 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@crowdin/cli@4.13.0': + '@crowdin/cli@4.14.0': dependencies: command-exists-promise: 2.0.2 node-fetch: 2.7.0 shelljs: 0.10.0 - tar: 7.5.4 + tar: 7.5.9 yauzl: 3.2.0 transitivePeerDependencies: - encoding @@ -7058,14 +7058,14 @@ snapshots: '@docsearch/css@3.9.0': {} - '@docsearch/react@3.9.0(@algolia/client-search@5.40.0)(@types/react@19.2.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)': + '@docsearch/react@3.9.0(@algolia/client-search@5.40.0)(@types/react@19.2.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)': dependencies: '@algolia/autocomplete-core': 1.17.9(@algolia/client-search@5.40.0)(algoliasearch@5.40.0)(search-insights@2.17.3) '@algolia/autocomplete-preset-algolia': 1.17.9(@algolia/client-search@5.40.0)(algoliasearch@5.40.0) '@docsearch/css': 3.9.0 algoliasearch: 5.40.0 optionalDependencies: - '@types/react': 19.2.9 + '@types/react': 19.2.14 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) search-insights: 2.17.3 @@ -7141,7 +7141,7 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': + '@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': dependencies: '@docusaurus/babel': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/bundler': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) @@ -7150,7 +7150,7 @@ snapshots: '@docusaurus/utils': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-common': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-validation': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@mdx-js/react': 3.1.1(@types/react@19.2.9)(react@19.0.0) + '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.0.0) boxen: 6.2.1 chalk: 4.1.2 chokidar: 3.6.0 @@ -7278,13 +7278,13 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/plugin-content-blog@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': + '@docusaurus/plugin-content-blog@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) '@docusaurus/logger': 3.9.2 '@docusaurus/mdx-loader': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/types': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-common': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -7320,13 +7320,13 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': + '@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) '@docusaurus/logger': 3.9.2 '@docusaurus/mdx-loader': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/module-type-aliases': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/types': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-common': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -7361,9 +7361,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-pages@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': + '@docusaurus/plugin-content-pages@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) '@docusaurus/mdx-loader': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/types': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -7392,9 +7392,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-css-cascade-layers@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': + '@docusaurus/plugin-css-cascade-layers@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) '@docusaurus/types': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-validation': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -7420,9 +7420,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-debug@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': + '@docusaurus/plugin-debug@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) '@docusaurus/types': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) fs-extra: 11.3.0 @@ -7449,9 +7449,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-analytics@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': + '@docusaurus/plugin-google-analytics@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) '@docusaurus/types': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-validation': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 @@ -7476,9 +7476,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-gtag@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': + '@docusaurus/plugin-google-gtag@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) '@docusaurus/types': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-validation': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/gtag.js': 0.0.12 @@ -7504,9 +7504,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-tag-manager@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': + '@docusaurus/plugin-google-tag-manager@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) '@docusaurus/types': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-validation': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 @@ -7531,9 +7531,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-sitemap@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': + '@docusaurus/plugin-sitemap@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) '@docusaurus/logger': 3.9.2 '@docusaurus/types': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -7563,9 +7563,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-svgr@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': + '@docusaurus/plugin-svgr@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) '@docusaurus/types': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-validation': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -7594,22 +7594,22 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/preset-classic@3.9.2(@algolia/client-search@5.40.0)(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(@types/react@19.2.9)(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.8.2)': - dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/plugin-css-cascade-layers': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/plugin-debug': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/plugin-google-analytics': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/plugin-google-gtag': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/plugin-google-tag-manager': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/plugin-sitemap': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/plugin-svgr': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/theme-classic': 3.9.2(@types/react@19.2.9)(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/theme-search-algolia': 3.9.2(@algolia/client-search@5.40.0)(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(@types/react@19.2.9)(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.8.2) + '@docusaurus/preset-classic@3.9.2(@algolia/client-search@5.40.0)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(@types/react@19.2.14)(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.8.2)': + dependencies: + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/plugin-css-cascade-layers': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/plugin-debug': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/plugin-google-analytics': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/plugin-google-gtag': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/plugin-google-tag-manager': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/plugin-sitemap': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/plugin-svgr': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/theme-classic': 3.9.2(@types/react@19.2.14)(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/theme-search-algolia': 3.9.2(@algolia/client-search@5.40.0)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(@types/react@19.2.14)(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.8.2) '@docusaurus/types': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) @@ -7637,25 +7637,25 @@ snapshots: '@docusaurus/react-loadable@6.0.0(react@19.0.0)': dependencies: - '@types/react': 19.2.9 + '@types/react': 19.2.14 react: 19.0.0 - '@docusaurus/theme-classic@3.9.2(@types/react@19.2.9)(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': + '@docusaurus/theme-classic@3.9.2(@types/react@19.2.14)(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) '@docusaurus/logger': 3.9.2 '@docusaurus/mdx-loader': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/module-type-aliases': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/theme-translations': 3.9.2 '@docusaurus/types': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-common': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-validation': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@mdx-js/react': 3.1.1(@types/react@19.2.9)(react@19.0.0) + '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.0.0) clsx: 2.1.1 infima: 0.2.0-alpha.45 lodash: 4.17.21 @@ -7688,15 +7688,15 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@docusaurus/mdx-loader': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/module-type-aliases': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) '@docusaurus/utils': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-common': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/history': 4.7.11 - '@types/react': 19.2.9 + '@types/react': 19.2.14 '@types/react-router-config': 5.0.11 clsx: 2.1.1 parse-numeric-range: 1.3.0 @@ -7713,13 +7713,13 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/theme-search-algolia@3.9.2(@algolia/client-search@5.40.0)(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(@types/react@19.2.9)(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.8.2)': + '@docusaurus/theme-search-algolia@3.9.2(@algolia/client-search@5.40.0)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(@types/react@19.2.14)(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.8.2)': dependencies: - '@docsearch/react': 3.9.0(@algolia/client-search@5.40.0)(@types/react@19.2.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3) - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docsearch/react': 3.9.0(@algolia/client-search@5.40.0)(@types/react@19.2.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) '@docusaurus/logger': 3.9.2 - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/theme-translations': 3.9.2 '@docusaurus/utils': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-validation': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -7766,7 +7766,7 @@ snapshots: dependencies: '@mdx-js/mdx': 3.1.0(acorn@8.15.0) '@types/history': 4.7.11 - '@types/react': 19.2.9 + '@types/react': 19.2.14 commander: 5.1.0 joi: 17.13.3 react: 19.0.0 @@ -8026,7 +8026,7 @@ snapshots: '@isaacs/fs-minipass@4.0.1': dependencies: - minipass: 7.1.2 + minipass: 7.1.3 '@jest/schemas@29.6.3': dependencies: @@ -8137,10 +8137,10 @@ snapshots: - acorn - supports-color - '@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0)': + '@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0)': dependencies: '@types/mdx': 2.0.13 - '@types/react': 19.2.9 + '@types/react': 19.2.14 react: 19.0.0 '@nodelib/fs.scandir@2.1.5': @@ -8550,13 +8550,13 @@ snapshots: '@types/react-router@5.1.20': dependencies: '@types/history': 4.7.11 - '@types/react': 19.2.9 + '@types/react': 19.2.14 '@types/react@19.1.12': dependencies: csstype: 3.1.3 - '@types/react@19.2.9': + '@types/react@19.2.14': dependencies: csstype: 3.2.3 @@ -9517,9 +9517,9 @@ snapshots: - styled-components - supports-color - docusaurus-theme-redoc@2.5.0(@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(core-js@3.42.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(webpack@5.99.8): + docusaurus-theme-redoc@2.5.0(@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(core-js@3.42.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(webpack@5.99.8): dependencies: - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@redocly/openapi-core': 1.16.0 clsx: 1.2.1 lodash: 4.17.21 @@ -9794,7 +9794,7 @@ snapshots: estree-util-value-to-estree@3.4.0: dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 estree-util-visit@2.0.0: dependencies: @@ -11270,11 +11270,11 @@ snapshots: minimist@1.2.8: {} - minipass@7.1.2: {} + minipass@7.1.3: {} minizlib@3.1.0: dependencies: - minipass: 7.1.2 + minipass: 7.1.3 mobx-react-lite@4.1.0(mobx@6.13.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: @@ -12262,12 +12262,12 @@ snapshots: - react-native - supports-color - redocusaurus@2.5.0(@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@docusaurus/utils@3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(core-js@3.42.0)(mobx@6.13.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(styled-components@6.1.18(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(webpack@5.99.8): + redocusaurus@2.5.0(@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@docusaurus/utils@3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(core-js@3.42.0)(mobx@6.13.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(styled-components@6.1.18(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(webpack@5.99.8): dependencies: - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils': 3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) docusaurus-plugin-redoc: 2.5.0(@docusaurus/utils@3.9.2(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(core-js@3.42.0)(mobx@6.13.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(styled-components@6.1.18(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) - docusaurus-theme-redoc: 2.5.0(@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.9)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(core-js@3.42.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(webpack@5.99.8) + docusaurus-theme-redoc: 2.5.0(@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.0.0))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(core-js@3.42.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(webpack@5.99.8) transitivePeerDependencies: - core-js - encoding @@ -12851,11 +12851,11 @@ snapshots: tapable@2.2.1: {} - tar@7.5.4: + tar@7.5.9: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 - minipass: 7.1.2 + minipass: 7.1.3 minizlib: 3.1.0 yallist: 5.0.0 From b524c785113d98fc5b485459a8ec39feba1d940a Mon Sep 17 00:00:00 2001 From: icyleaf Date: Wed, 11 Mar 2026 10:38:05 +0800 Subject: [PATCH 14/26] fix: pnpm lock --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 85b18f63f..6283c757d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,7 +55,7 @@ importers: specifier: ^7.18.6 version: 7.18.6 '@crowdin/cli': - specifier: ^4.13.0 + specifier: ^4.14.0 version: 4.14.0 '@docusaurus/module-type-aliases': specifier: ^3.9.2 From 9b48620028ff4cd2e07be528d00985ad117939e2 Mon Sep 17 00:00:00 2001 From: icyleaf Date: Wed, 11 Mar 2026 10:52:04 +0800 Subject: [PATCH 15/26] fix: wrap env fetch error on compile --- package.json | 2 +- src/lib/crypto.ts | 12 ++++++++---- src/lib/query.ts | 12 ++++++++---- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 9334510ea..a04e7e45f 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,6 @@ ] }, "engines": { - "node": ">=18.0" + "node": ">=22.0" } } diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts index dfc8d9d4f..d6cb7b20c 100644 --- a/src/lib/crypto.ts +++ b/src/lib/crypto.ts @@ -2,9 +2,13 @@ * Encryption utility functions - using AES-GCM algorithm */ -const salt = process.env.ZEALOT_ENCRYPTION_SALT; -if (!salt) { - throw new Error("ZEALOT_ENCRYPTION_SALT is not defined"); +const salt = process.env.ZEALOT_ENCRYPTION_SALT || 'default-salt-for-ssr-build'; + +function getSalt(): string { + if (typeof window !== 'undefined' && salt === 'default-salt-for-ssr-build') { + throw new Error("ZEALOT_ENCRYPTION_SALT is not defined"); + } + return salt; } /** @@ -28,7 +32,7 @@ async function generateKey(keyString: string): Promise { return crypto.subtle.deriveKey( { name: 'PBKDF2', - salt: encoder.encode(salt), + salt: encoder.encode(getSalt()), iterations: 100000, hash: 'SHA-256', }, diff --git a/src/lib/query.ts b/src/lib/query.ts index 2bcefd404..c7c2d2157 100644 --- a/src/lib/query.ts +++ b/src/lib/query.ts @@ -2,9 +2,13 @@ import { useQuery, type UseQueryResult } from "@tanstack/react-query"; import { encrypt } from "@site/src/lib/crypto"; import type { GatewayClient, OrdersResponse } from "@site/src/types"; -const secretKey = process.env.ZEALOT_ENCRYPTION_KEY; -if (!secretKey) { - throw new Error("ZEALOT_ENCRYPTION_KEY is not defined"); +const secretKey = process.env.ZEALOT_ENCRYPTION_KEY || 'default-key-for-ssr-build'; + +function getSecretKey(): string { + if (typeof window !== 'undefined' && secretKey === 'default-key-for-ssr-build') { + throw new Error("ZEALOT_ENCRYPTION_KEY is not defined"); + } + return secretKey; } /** @@ -26,7 +30,7 @@ export const useCustomerOrders = ( throw new Error("Gateway is not initialized"); } // Encrypt email - const encryptedEmail = await encrypt(email, secretKey); + const encryptedEmail = await encrypt(email, getSecretKey()); return gateway.orders(encryptedEmail); }, enabled: !!email && !!gateway && enabled, From d6396904c595f39aff6f819f3fad78cd480b3891 Mon Sep 17 00:00:00 2001 From: icyleaf Date: Thu, 12 Mar 2026 13:10:02 +0800 Subject: [PATCH 16/26] fix: amount price --- src/pages/orders.tsx | 278 +++++++++++++++++++++++++++++++------------ 1 file changed, 202 insertions(+), 76 deletions(-) diff --git a/src/pages/orders.tsx b/src/pages/orders.tsx index c6a6eee6f..ed2ac487d 100644 --- a/src/pages/orders.tsx +++ b/src/pages/orders.tsx @@ -6,7 +6,11 @@ import { useHistory } from "@docusaurus/router"; import { useGateway } from "@site/src/hooks/useGateway"; import { useCustomerOrders } from "@site/src/lib/query"; import { decrypt } from "@site/src/lib/crypto"; -import { OutlineButton, PrimaryButton, SecondaryButton } from "@site/src/components/ui/Button"; +import { + OutlineButton, + PrimaryButton, + SecondaryButton, +} from "@site/src/components/ui/Button"; import Translate, { translate } from "@docusaurus/Translate"; import type { Order } from "@site/src/types"; @@ -37,12 +41,24 @@ function OrdersClient(): JSX.Element { .catch(() => { // 解密失败,清除数据并显示错误提示 sessionStorage.removeItem("ordersEmail"); - setSessionError(translate({ id: "orders.sessionError.invalid", message: "Session data is invalid. Please enter your email to view orders." })); + setSessionError( + translate({ + id: "orders.sessionError.invalid", + message: + "Session data is invalid. Please enter your email to view orders.", + }), + ); }); } else { // 没有加密密钥,无法解密,清除数据 sessionStorage.removeItem("ordersEmail"); - setSessionError(translate({ id: "orders.sessionError.config", message: "Session configuration error. Please enter your email to view orders." })); + setSessionError( + translate({ + id: "orders.sessionError.config", + message: + "Session configuration error. Please enter your email to view orders.", + }), + ); } } }, []); @@ -67,20 +83,30 @@ function OrdersClient(): JSX.Element { return (
{!hasSubmitted ? ( -
+

- Enter your email to view orders + + Enter your email to view orders +

{sessionError && (
-

{sessionError}

+

+ {sessionError} +

)} setUserEmail(e.target.value)} - placeholder={translate({ id: "orders.email.placeholder", message: "your@email.com" })} + placeholder={translate({ + id: "orders.email.placeholder", + message: "your@email.com", + })} className="w-full px-5 py-3 text-lg border-2 border-[var(--ifm-color-gray-300)] rounded-lg focus:outline-none focus:border-[var(--ifm-color-primary)] bg-[var(--ifm-background-color)] text-[var(--ifm-font-color-base)] placeholder:text-[var(--ifm-color-gray-600)] transition-colors" required /> @@ -97,51 +123,74 @@ function OrdersClient(): JSX.Element {

- Error loading your orders. + + Error loading your orders. +

setHasSubmitted(false)}> Try Again
- ) : (query.isLoading || !query.data) && ( -
-
-

- Loading your orders... -

-
+ ) : ( + (query.isLoading || !query.data) && ( +
+
+

+ + Loading your orders... + +

+
+ ) )} {query.data && (

- Your Orders + + Your Orders +

- {submittedEmail} + + {submittedEmail} +
- {fromCheckout && query.data.orders && query.data.orders.length > 0 && ( -
-

- You already have an order associated with this email. -

-
- )} + {fromCheckout && + query.data.orders && + query.data.orders.length > 0 && ( +
+

+ + You already have an order associated with this email. + +

+
+ )} {query.data.orders?.length === 0 ? (

- No orders found for this email. + + No orders found for this email. +

- Back to Pricing + + Back to Pricing + - setHasSubmitted(false)} className="flex-1"> - Search Another Email + setHasSubmitted(false)} + className="flex-1" + > + + Search Another Email +
@@ -152,19 +201,29 @@ function OrdersClient(): JSX.Element { {query.data.customer && (

- Customer + + Customer +

- Email + + Email + + + + {query.data.customer.email} - {query.data.customer.email}
- Customer ID + + Customer ID + + + + {query.data.customer.id} - {query.data.customer.id}
)} @@ -173,26 +232,42 @@ function OrdersClient(): JSX.Element { {query.data.license && (

- License + + License +

- License Key + + License Key + + + + {query.data.license.key} - {query.data.license.key}
- Status + + Status + - + {query.data.license.active - ? translate({ id: "orders.license.status.active", message: "Active" }) - : translate({ id: "orders.license.status.inactive", message: "Inactive" })} + ? translate({ + id: "orders.license.status.active", + message: "Active", + }) + : translate({ + id: "orders.license.status.inactive", + message: "Inactive", + })}
@@ -217,15 +292,20 @@ function OrdersClient(): JSX.Element {
- Amount + + Amount + - {(order.totalAmount).toFixed(2)} {order.currency} + {order.totalAmount.toFixed(2) / 100}{" "} + {order.currency}
- Created + + Created + {new Date(order.createdAt).toLocaleDateString()} @@ -233,68 +313,100 @@ function OrdersClient(): JSX.Element {
- Paid At + + Paid At + - {order.paidAt ? new Date(order.paidAt).toLocaleDateString() : '-'} + {order.paidAt + ? new Date(order.paidAt).toLocaleDateString() + : "-"}
- Status + + Status + - - {order.status === 'completed' - ? translate({ id: "orders.order.status.completed", message: "Completed" }) - : translate({ id: "orders.order.status.pending", message: "Pending" })} + + {order.status === "completed" + ? translate({ + id: "orders.order.status.completed", + message: "Completed", + }) + : translate({ + id: "orders.order.status.pending", + message: "Pending", + })}
))} - {/* Subscription Info */} {query.data.subscription && (

- Subscription + + Subscription +

- Status + + Status + - - {query.data.subscription.status.charAt(0).toUpperCase() + query.data.subscription.status.slice(1)} + + {query.data.subscription.status + .charAt(0) + .toUpperCase() + + query.data.subscription.status.slice(1)}
- Active At + + Active At + - {new Date(query.data.subscription.currentPeriodStart).toLocaleDateString()} + {new Date( + query.data.subscription.currentPeriodStart, + ).toLocaleDateString()}
- Expires At + + Expires At + - {new Date(query.data.subscription.currentPeriodEnd).toLocaleDateString()} + {new Date( + query.data.subscription.currentPeriodEnd, + ).toLocaleDateString()}
{query.data.subscription.cancelAtPeriodEnd && (

- Will cancel at period end + + Will cancel at period end +

)} @@ -309,14 +421,22 @@ function OrdersClient(): JSX.Element { className="flex-1" > {query.isFetching - ? translate({ id: "orders.refreshing", message: "Refreshing..." }) - : translate({ id: "orders.refresh", message: "Refresh" })} + ? translate({ + id: "orders.refreshing", + message: "Refreshing...", + }) + : translate({ + id: "orders.refresh", + message: "Refresh", + })} setHasSubmitted(false)} className="flex-1" > - Search Another Email + + Search Another Email +
@@ -332,7 +452,13 @@ function OrdersClient(): JSX.Element { export default function OrdersPage(): JSX.Element { return ( - {translate({ id: "orders.loading", message: "Loading..." })}
}> + + {translate({ id: "orders.loading", message: "Loading..." })} +
+ } + > {() => } From eb7a383a2d59fca43e2e28d9eb945208d771e49f Mon Sep 17 00:00:00 2001 From: icyleaf Date: Thu, 12 Mar 2026 13:17:06 +0800 Subject: [PATCH 17/26] refactor: orders accept email as query param --- src/pages/checkout/index.tsx | 6 ++---- src/pages/orders.tsx | 37 ++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx index fffdd32cf..b60d2e6cd 100644 --- a/src/pages/checkout/index.tsx +++ b/src/pages/checkout/index.tsx @@ -109,12 +109,10 @@ function CheckoutClient(): JSX.Element { const secretKey = process.env.ZEALOT_ENCRYPTION_KEY; if (secretKey) { encrypt(submittedEmail, secretKey).then((encryptedEmail) => { - sessionStorage.setItem("ordersEmail", encryptedEmail); - history.push(`${basePath}/orders`); + history.push(`${basePath}/orders?email=${encodeURIComponent(encryptedEmail)}`); }); } else { - sessionStorage.setItem("ordersEmail", submittedEmail); - history.push(`${basePath}/orders`); + history.push(`${basePath}/orders?email=${encodeURIComponent(submittedEmail)}`); } return; } diff --git a/src/pages/orders.tsx b/src/pages/orders.tsx index ed2ac487d..377bef79e 100644 --- a/src/pages/orders.tsx +++ b/src/pages/orders.tsx @@ -18,45 +18,46 @@ function OrdersClient(): JSX.Element { const { i18n } = useDocusaurusContext(); const history = useHistory(); + const urlParams = typeof window !== "undefined" + ? new URLSearchParams(window.location.search) + : null; + const [userEmail, setUserEmail] = useState(""); const [submittedEmail, setSubmittedEmail] = useState(""); const [hasSubmitted, setHasSubmitted] = useState(false); const [fromCheckout, setFromCheckout] = useState(false); - const [sessionError, setSessionError] = useState(null); + const [queryError, setQueryError] = useState(null); - // 从 sessionStorage 读取预填充的 email(从 checkout 页面跳转过来) + // 从 URL query 参数读取预填充的 email(从 checkout 页面跳转过来) useEffect(() => { - const storedEmail = sessionStorage.getItem("ordersEmail"); - if (storedEmail) { + const emailParam = urlParams?.get("email"); + if (emailParam) { const secretKey = process.env.ZEALOT_ENCRYPTION_KEY; if (secretKey) { - decrypt(storedEmail, secretKey) + decrypt(emailParam, secretKey) .then((decryptedEmail) => { setUserEmail(decryptedEmail); setSubmittedEmail(decryptedEmail); setHasSubmitted(true); setFromCheckout(true); - sessionStorage.removeItem("ordersEmail"); }) .catch(() => { - // 解密失败,清除数据并显示错误提示 - sessionStorage.removeItem("ordersEmail"); - setSessionError( + // 解密失败,显示错误提示 + setQueryError( translate({ - id: "orders.sessionError.invalid", + id: "orders.queryError.invalid", message: - "Session data is invalid. Please enter your email to view orders.", + "Invalid email parameter. Please enter your email to view orders.", }), ); }); } else { - // 没有加密密钥,无法解密,清除数据 - sessionStorage.removeItem("ordersEmail"); - setSessionError( + // 没有加密密钥,无法解密 + setQueryError( translate({ - id: "orders.sessionError.config", + id: "orders.queryError.config", message: - "Session configuration error. Please enter your email to view orders.", + "Configuration error. Please enter your email to view orders.", }), ); } @@ -92,10 +93,10 @@ function OrdersClient(): JSX.Element { Enter your email to view orders - {sessionError && ( + {queryError && (

- {sessionError} + {queryError}

)} From 7be3803341da05a0954053ffc7a32658e6b76418 Mon Sep 17 00:00:00 2001 From: icyleaf Date: Thu, 12 Mar 2026 13:24:07 +0800 Subject: [PATCH 18/26] fix: my orders page not update url --- src/pages/orders.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/pages/orders.tsx b/src/pages/orders.tsx index 377bef79e..30fe6db9f 100644 --- a/src/pages/orders.tsx +++ b/src/pages/orders.tsx @@ -5,7 +5,7 @@ import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; import { useHistory } from "@docusaurus/router"; import { useGateway } from "@site/src/hooks/useGateway"; import { useCustomerOrders } from "@site/src/lib/query"; -import { decrypt } from "@site/src/lib/crypto"; +import { decrypt, encrypt } from "@site/src/lib/crypto"; import { OutlineButton, PrimaryButton, @@ -69,6 +69,20 @@ function OrdersClient(): JSX.Element { const { gateway } = useGateway(); const query = useCustomerOrders(gateway, submittedEmail, hasSubmitted); + // 当成功获取到订单数据后,更新 URL 添加 email 参数 + useEffect(() => { + if (query.data && query.data.orders.length > 0 && submittedEmail && !fromCheckout) { + const secretKey = process.env.ZEALOT_ENCRYPTION_KEY; + if (secretKey) { + encrypt(submittedEmail, secretKey).then((encryptedEmail) => { + const newUrl = `${window.location.pathname}?email=${encodeURIComponent(encryptedEmail)}`; + window.history.replaceState({}, "", newUrl); + }); + } + // 没有 secretKey 时不更新 URL,避免明文暴露 email + } + }, [query.data, submittedEmail, fromCheckout]); + const handleEmailSubmit = (e: React.FormEvent) => { e.preventDefault(); if (userEmail.trim()) { From 217d9495e90b0873e57855045500ee5df9b6092f Mon Sep 17 00:00:00 2001 From: icyleaf Date: Thu, 12 Mar 2026 14:48:45 +0800 Subject: [PATCH 19/26] chore: cleanup --- docusaurus.config.ts | 11 +- package.json | 8 +- pnpm-lock.yaml | 2 +- postcss.config.js | 5 - sidebars.ts | 1 + src/components/FeatureSection/index.tsx | 62 +++++++---- src/components/HomepageHeader/index.tsx | 76 +++++++++----- src/pages/checkout/index.tsx | 77 +++++++++----- src/pages/checkout/success.tsx | 5 +- src/pages/donate.tsx | 132 +++++++++++++++++------- src/pages/index.tsx | 23 +++-- src/pages/orders.tsx | 30 +++--- src/pages/pricing.tsx | 4 +- tsconfig.json | 22 +--- 14 files changed, 284 insertions(+), 174 deletions(-) delete mode 100644 postcss.config.js diff --git a/docusaurus.config.ts b/docusaurus.config.ts index ebd4bfee1..51ebf60fd 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -1,7 +1,7 @@ // @ts-check // Note: type annotations allow type checking and IDEs autocompletion -import type {Config} from '@docusaurus/types'; -import type * as Preset from '@docusaurus/preset-classic'; +import type { Config } from "@docusaurus/types"; +import type * as Preset from "@docusaurus/preset-classic"; const isDev = process.env.NODE_ENV === "development"; @@ -34,7 +34,6 @@ function getNextVersionName() { return "Next"; } -/** @type {import('@docusaurus/types').Config} */ const config: Config = { title: "Zealot", tagline: @@ -111,7 +110,7 @@ const config: Config = { themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ - ({ + { navbar: { title: "Zealot", logo: { @@ -255,7 +254,7 @@ const config: Config = { // content: `⭐️ If you like Zealot, give it a star on GitHub and vote on ProductHunt`, // isCloseable: true // }, - }), + }, plugins: [ ["./src/plugins/tailwind-config.ts", {}], @@ -282,4 +281,4 @@ const config: Config = { }, }; -module.exports = config; +export default config; diff --git a/package.json b/package.json index a04e7e45f..102caf1cd 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@docusaurus/preset-classic": "^3.9.2", "@docusaurus/tsconfig": "^3.9.2", "@gracefullight/docusaurus-plugin-microsoft-clarity": "^1.0.0", - "@mdx-js/react": "^3.1.1", + "@mdx-js/react": "^3.0.0", "@paddle/paddle-js": "^1.6.1", "@tanstack/react-query": "^5.90.21", "@tanstack/react-query-devtools": "^5.91.3", @@ -54,9 +54,9 @@ "not op_mini all" ], "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" + "last 3 chrome version", + "last 3 firefox version", + "last 5 safari version" ] }, "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6283c757d..f5735f080 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,7 +21,7 @@ importers: specifier: ^1.0.0 version: 1.0.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mdx-js/react': - specifier: ^3.1.1 + specifier: ^3.0.0 version: 3.1.1(@types/react@19.2.14)(react@19.0.0) '@paddle/paddle-js': specifier: ^1.6.1 diff --git a/postcss.config.js b/postcss.config.js deleted file mode 100644 index 483f37854..000000000 --- a/postcss.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - plugins: { - "@tailwindcss/postcss": {}, - }, -}; diff --git a/sidebars.ts b/sidebars.ts index 6066b8d2e..6af6d94bc 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -1,4 +1,5 @@ import type { SidebarsConfig } from "@docusaurus/plugin-content-docs"; + import selfHosted from "./docs/self-hosted/sidebars"; import userGuide from "./docs/user-guide/sidebars"; import developerGuide from "./docs/developer-guide/sidebars"; diff --git a/src/components/FeatureSection/index.tsx b/src/components/FeatureSection/index.tsx index e0214c51f..51a38f3c0 100644 --- a/src/components/FeatureSection/index.tsx +++ b/src/components/FeatureSection/index.tsx @@ -1,8 +1,8 @@ -import React from 'react'; -import Translate from '@docusaurus/Translate'; -import styles from './styles.module.css'; +import React, { ReactNode } from "react"; +import Translate from "@docusaurus/Translate"; +import styles from "./styles.module.css"; -export default function FeatureSection(): JSX.Element { +export default function FeatureSection() { return (
@@ -12,15 +12,18 @@ export default function FeatureSection(): JSX.Element {

+ description="Easy Deployment feature title" + > Easy Deployment

- Deploy with Docker in minutes. Self-hosted solution with complete control over your data. + description="Easy Deployment feature description" + > + Deploy with Docker in minutes. Self-hosted solution with + complete control over your data.

@@ -30,15 +33,18 @@ export default function FeatureSection(): JSX.Element {

+ description="Multi-Platform feature title" + > Multi-Platform

- Support for Android, iOS, macOS, Windows, and Linux apps. One solution for all platforms. + description="Multi-Platform feature description" + > + Support for Android, iOS, macOS, Windows, and Linux apps. One + solution for all platforms.

@@ -48,15 +54,18 @@ export default function FeatureSection(): JSX.Element {

+ description="CI/CD Integration feature title" + > CI/CD Integration

- Seamlessly integrate with your existing CI/CD pipeline for automated app distribution. + description="CI/CD Integration feature description" + > + Seamlessly integrate with your existing CI/CD pipeline for + automated app distribution.

@@ -66,15 +75,18 @@ export default function FeatureSection(): JSX.Element {

+ description="Team Management feature title" + > Team Management

- Manage team members and control access permissions. Organize apps by channels and manage user roles effectively. + description="Team Management feature description" + > + Manage team members and control access permissions. Organize + apps by channels and manage user roles effectively.

@@ -84,15 +96,18 @@ export default function FeatureSection(): JSX.Element {

+ description="Developer Friendly feature title" + > Developer Friendly

- Rich SDKs and RESTful APIs for seamless integration. Support for multiple programming languages and platforms. + description="Developer Friendly feature description" + > + Rich SDKs and RESTful APIs for seamless integration. Support for + multiple programming languages and platforms.

@@ -102,15 +117,18 @@ export default function FeatureSection(): JSX.Element {

+ description="Secure & Private feature title" + > Secure & Private

- Keep your apps and data secure with enterprise-grade security and privacy controls. + description="Secure & Private feature description" + > + Keep your apps and data secure with enterprise-grade security + and privacy controls.

diff --git a/src/components/HomepageHeader/index.tsx b/src/components/HomepageHeader/index.tsx index 01ca2df65..de26ded8a 100644 --- a/src/components/HomepageHeader/index.tsx +++ b/src/components/HomepageHeader/index.tsx @@ -1,56 +1,75 @@ -import React from 'react'; -import clsx from 'clsx'; -import Link from '@docusaurus/Link'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import Translate, { translate } from '@docusaurus/Translate'; -import ThemedImage from '@theme/ThemedImage'; +import React, { ReactNode } from "react"; +import clsx from "clsx"; +import Link from "@docusaurus/Link"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import Translate, { translate } from "@docusaurus/Translate"; +import ThemedImage from "@theme/ThemedImage"; -import styles from './styles.module.css'; +import styles from "./styles.module.css"; -export default function HomepageHeader(): JSX.Element { +export default function HomepageHeader() { const { i18n } = useDocusaurusContext(); const currentLocale = i18n.currentLocale; - const imgPath = currentLocale === 'en' ? '/img' : `/img/${currentLocale}`; + const imgPath = currentLocale === "en" ? "/img" : `/img/${currentLocale}`; return ( -
+

+ description="The main title for homepage" + > Self-hosted Beta App Distribution

- Deploy and distribute your Android, iOS, macOS, Windows and Linux apps with ease. - Open source, self-hosted solution for managing beta releases and app lifecycle. + description="The subtitle description for homepage" + > + Deploy and distribute your Android, iOS, macOS, Windows and + Linux apps with ease. Open source, self-hosted solution for + managing beta releases and app lifecycle.

+ className={clsx( + "button button--primary button--lg", + styles.primaryButton, + )} + to="/docs/user-guide/" + > + description="Get started button text" + > Get Started - + className={clsx( + "button button--outline button--lg", + styles.secondaryButton, + )} + href="https://github.com/tryzealot/zealot" + > + + description="View on GitHub button text" + > View on GitHub @@ -61,9 +80,9 @@ export default function HomepageHeader(): JSX.Element {
+ description="Supported platforms count" + > Platforms @@ -88,7 +108,8 @@ export default function HomepageHeader(): JSX.Element { + description="Unlimited apps" + > Apps @@ -98,7 +119,8 @@ export default function HomepageHeader(): JSX.Element { + description="Open source" + > Open Source diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx index b60d2e6cd..09b37af76 100644 --- a/src/pages/checkout/index.tsx +++ b/src/pages/checkout/index.tsx @@ -1,6 +1,6 @@ +import type { ReactNode } from "react"; import { useEffect, useMemo, useState } from "react"; import Layout from "@theme/Layout"; -import BrowserOnly from "@docusaurus/BrowserOnly"; import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; import { useHistory } from "@docusaurus/router"; import Translate, { translate } from "@docusaurus/Translate"; @@ -16,7 +16,7 @@ import type { PaddleCheckoutOpenOptions, } from "@site/src/types"; -function CheckoutClient(): JSX.Element { +function CheckoutClient(): ReactNode { const { i18n } = useDocusaurusContext(); const history = useHistory(); @@ -25,7 +25,7 @@ function CheckoutClient(): JSX.Element { typeof window !== "undefined" ? new URLSearchParams(window.location.search) : null, - [] + [], ); const priceId = urlParams?.get("id"); @@ -61,7 +61,10 @@ function CheckoutClient(): JSX.Element { priceId: data.data.items?.[0]?.price_id || "", amount: data.data.totals?.total, }; - sessionStorage.setItem("paddleTransaction", JSON.stringify(transactionData)); + sessionStorage.setItem( + "paddleTransaction", + JSON.stringify(transactionData), + ); history.push(`${basePath}/checkout/success`); break; @@ -109,10 +112,14 @@ function CheckoutClient(): JSX.Element { const secretKey = process.env.ZEALOT_ENCRYPTION_KEY; if (secretKey) { encrypt(submittedEmail, secretKey).then((encryptedEmail) => { - history.push(`${basePath}/orders?email=${encodeURIComponent(encryptedEmail)}`); + history.push( + `${basePath}/orders?email=${encodeURIComponent(encryptedEmail)}`, + ); }); } else { - history.push(`${basePath}/orders?email=${encodeURIComponent(submittedEmail)}`); + history.push( + `${basePath}/orders?email=${encodeURIComponent(submittedEmail)}`, + ); } return; } @@ -142,22 +149,40 @@ function CheckoutClient(): JSX.Element { paddle.Checkout.open(options); paddle.Spinner.show(); } - }, [query.data, paddle, priceId, quantity, discountCode, submittedEmail, countryCode, history, basePath]); + }, [ + query.data, + paddle, + priceId, + quantity, + discountCode, + submittedEmail, + countryCode, + history, + basePath, + ]); return (
{!hasSubmitted ? ( - +

- Enter your email to continue + + Enter your email to continue +

setUserEmail(e.target.value)} - placeholder={translate({ id: "checkout.email.placeholder", message: "your@email.com" })} + placeholder={translate({ + id: "checkout.email.placeholder", + message: "your@email.com", + })} className="w-full px-5 py-3 text-lg border-2 border-[var(--color-base-300)] rounded-lg focus:outline-none focus:border-[var(--color-primary)] bg-[var(--color-base-100)] text-[var(--color-base-content)] placeholder:text-[var(--semantic-text-muted)] transition-colors" required /> @@ -170,23 +195,31 @@ function CheckoutClient(): JSX.Element { {query.isError ? (

- Error loading your orders. -

-
- ) : (query.isLoading || !query.data) && ( -
-
-

- Preparing checkout... + + Error loading your orders. +

+ ) : ( + (query.isLoading || !query.data) && ( +
+
+

+ + Preparing checkout... + +

+
+ ) )} {query.data && query.data.orders?.length > 0 && (

- Redirecting to your orders... + + Redirecting to your orders... +

)} @@ -196,12 +229,10 @@ function CheckoutClient(): JSX.Element { ); } -export default function CheckoutPage(): JSX.Element { +export default function CheckoutPage(): ReactNode { return ( - {translate({ id: "checkout.loading", message: "Loading checkout..." })}
}> - {() => } - + ); } diff --git a/src/pages/checkout/success.tsx b/src/pages/checkout/success.tsx index aa2ccad27..192b49614 100644 --- a/src/pages/checkout/success.tsx +++ b/src/pages/checkout/success.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from "react"; import Layout from "@theme/Layout"; import BrowserOnly from "@docusaurus/BrowserOnly"; import { useEffect, useState } from "react"; @@ -6,7 +7,7 @@ import { PrimaryButton } from "@site/src/components/ui/Button"; import Translate, { translate } from "@docusaurus/Translate"; import type { LicenseResponse, TransactionData } from "@site/src/types"; -function SuccessClient(): JSX.Element { +function SuccessClient(): ReactNode { const { gateway } = useGateway(); const [isLoading, setIsLoading] = useState(true); const [license, setLicense] = useState(null); @@ -186,7 +187,7 @@ function SuccessClient(): JSX.Element { ); } -export default function CheckoutSuccessPage(): JSX.Element { +export default function CheckoutSuccessPage(): ReactNode { return ( {translate({ id: "checkout.success.loading", message: "Loading..." })}
}> diff --git a/src/pages/donate.tsx b/src/pages/donate.tsx index fb5ce6bb4..1be53e07e 100644 --- a/src/pages/donate.tsx +++ b/src/pages/donate.tsx @@ -1,46 +1,72 @@ -import React, { CSSProperties } from 'react'; -import Layout from '@theme/Layout'; -import useBaseUrl from '@docusaurus/useBaseUrl'; -import Translate, { translate } from '@docusaurus/Translate'; +import type { ReactNode } from "react"; +import { CSSProperties } from "react"; +import Layout from "@theme/Layout"; +import useBaseUrl from "@docusaurus/useBaseUrl"; +import Translate, { translate } from "@docusaurus/Translate"; const linkStyle: CSSProperties = { - display: 'block', + display: "block", fontWeight: 600, fontSize: 18, borderRadius: 8, - padding: '16px 0', - textAlign: 'center', - textDecoration: 'none', - boxShadow: '0 2px 8px rgba(123,111,240,0.08)', + padding: "16px 0", + textAlign: "center", + textDecoration: "none", + boxShadow: "0 2px 8px rgba(123,111,240,0.08)", }; -export default function Donate(): JSX.Element { +export default function DonatePage(): ReactNode { return ( - -
-

Support Zealot

+ +
+

+ Support Zealot +

- Zealot is an open-source project dedicated to providing a simple and easy-to-use internal distribution platform for mobile app developers. If Zealot has helped your work, you are welcome to support the ongoing maintenance and development of the project through the following ways. + Zealot is an open-source project dedicated to providing a simple and + easy-to-use internal distribution platform for mobile app + developers. If Zealot has helped your work, you are welcome to + support the ongoing maintenance and development of the project + through the following ways.

-

Donation Channels

-
+

+ Donation Channels +

+
-

WeChat / Alipay

-
+

+ WeChat / Alipay +

+
- WeChat Donate QR -
WeChat
+ WeChat Donate QR +
+ WeChat +
- Alipay Donate QR -
Alipay
+ Alipay Donate QR +
+ Alipay +
-

- Thank you for choosing Zealot as your app distribution solution. +

+ + Thank you for choosing Zealot as your app distribution solution. +

diff --git a/src/pages/index.tsx b/src/pages/index.tsx index ad2e152be..c3987ba2d 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,24 +1,27 @@ -import Layout from '@theme/Layout'; -import { translate } from '@docusaurus/Translate'; -import HomepageHeader from '@site/src/components/HomepageHeader'; -import FeatureSection from '@site/src/components/FeatureSection'; +import Layout from "@theme/Layout"; +import { translate } from "@docusaurus/Translate"; +import HomepageHeader from "@site/src/components/HomepageHeader"; +import FeatureSection from "@site/src/components/FeatureSection"; +import type { ReactNode } from "react"; -export default function Home(): JSX.Element { +export default function Home(): ReactNode { return ( - +
+ +
); } diff --git a/src/pages/orders.tsx b/src/pages/orders.tsx index 30fe6db9f..56f012657 100644 --- a/src/pages/orders.tsx +++ b/src/pages/orders.tsx @@ -1,6 +1,6 @@ +import type { ReactNode } from "react"; import { useState, useEffect } from "react"; import Layout from "@theme/Layout"; -import BrowserOnly from "@docusaurus/BrowserOnly"; import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; import { useHistory } from "@docusaurus/router"; import { useGateway } from "@site/src/hooks/useGateway"; @@ -14,13 +14,14 @@ import { import Translate, { translate } from "@docusaurus/Translate"; import type { Order } from "@site/src/types"; -function OrdersClient(): JSX.Element { +function OrdersClient(): ReactNode { const { i18n } = useDocusaurusContext(); const history = useHistory(); - const urlParams = typeof window !== "undefined" - ? new URLSearchParams(window.location.search) - : null; + const urlParams = + typeof window !== "undefined" + ? new URLSearchParams(window.location.search) + : null; const [userEmail, setUserEmail] = useState(""); const [submittedEmail, setSubmittedEmail] = useState(""); @@ -71,7 +72,12 @@ function OrdersClient(): JSX.Element { // 当成功获取到订单数据后,更新 URL 添加 email 参数 useEffect(() => { - if (query.data && query.data.orders.length > 0 && submittedEmail && !fromCheckout) { + if ( + query.data && + query.data.orders.length > 0 && + submittedEmail && + !fromCheckout + ) { const secretKey = process.env.ZEALOT_ENCRYPTION_KEY; if (secretKey) { encrypt(submittedEmail, secretKey).then((encryptedEmail) => { @@ -464,18 +470,10 @@ function OrdersClient(): JSX.Element { ); } -export default function OrdersPage(): JSX.Element { +export default function OrdersPage(): ReactNode { return ( - - {translate({ id: "orders.loading", message: "Loading..." })} -
- } - > - {() => } - + ); } diff --git a/src/pages/pricing.tsx b/src/pages/pricing.tsx index d83fb99a4..44f1d3095 100644 --- a/src/pages/pricing.tsx +++ b/src/pages/pricing.tsx @@ -1,11 +1,11 @@ -import React from 'react'; +import type { ReactNode } from 'react'; import Layout from '@theme/Layout'; import { translate } from '@docusaurus/Translate'; import { EnterpriseTier } from '@site/src/constants/pricing-tier'; import { EnterpriseCard } from '@site/src/components/Pricing/EnterpriseCard'; import { PriceCards } from '@site/src/components/Pricing/PriceCards'; -export default function PricingPage(): JSX.Element { +export default function PricingPage(): ReactNode { return ( Date: Thu, 12 Mar 2026 15:55:03 +0800 Subject: [PATCH 20/26] style: fix dark mode for each block --- src/pages/orders.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/pages/orders.tsx b/src/pages/orders.tsx index 56f012657..5215ffc0b 100644 --- a/src/pages/orders.tsx +++ b/src/pages/orders.tsx @@ -85,10 +85,15 @@ function OrdersClient(): ReactNode { window.history.replaceState({}, "", newUrl); }); } - // 没有 secretKey 时不更新 URL,避免明文暴露 email } }, [query.data, submittedEmail, fromCheckout]); + useEffect(() => { + if (userEmail !== "" && !hasSubmitted) return; + + window.history.replaceState({}, "", window.location.pathname); + }, [userEmail, hasSubmitted]); + const handleEmailSubmit = (e: React.FormEvent) => { e.preventDefault(); if (userEmail.trim()) { @@ -220,7 +225,7 @@ function OrdersClient(): ReactNode {
{/* Customer Info */} {query.data.customer && ( -
+

Customer @@ -251,7 +256,7 @@ function OrdersClient(): ReactNode { {/* License Info */} {query.data.license && ( -
+

License @@ -298,7 +303,7 @@ function OrdersClient(): ReactNode { {query.data.orders.map((order: Order) => (

Order @@ -373,7 +378,7 @@ function OrdersClient(): ReactNode { {/* Subscription Info */} {query.data.subscription && ( -
+

Subscription From 218230e844ed12908161932bf0bd025d29ab5234 Mon Sep 17 00:00:00 2001 From: icyleaf Date: Thu, 12 Mar 2026 17:42:24 +0800 Subject: [PATCH 21/26] chore: remove logging --- src/components/Pricing/index.tsx | 23 ++++-- src/pages/checkout/success.tsx | 134 +++++++++++++++++++++++-------- src/pages/orders.tsx | 11 +-- 3 files changed, 120 insertions(+), 48 deletions(-) diff --git a/src/components/Pricing/index.tsx b/src/components/Pricing/index.tsx index 53bf080fb..79ab8d875 100644 --- a/src/components/Pricing/index.tsx +++ b/src/components/Pricing/index.tsx @@ -8,7 +8,11 @@ import { PriceTitle } from "@site/src/components/Pricing/PriceTitle"; import { PriceAmount } from "@site/src/components/Pricing/PriceAmount"; import { FeaturesList } from "@site/src/components/Pricing/FeaturesList"; import { PricingButton } from "@site/src/components/Pricing/PriceButton"; -import type { PaddleInstance, PricingTierItem, PaddleCheckoutItem } from "@site/src/types"; +import type { + PaddleInstance, + PricingTierItem, + PaddleCheckoutItem, +} from "@site/src/types"; interface PricingCardProps { tier: PricingTierItem; @@ -17,7 +21,11 @@ interface PricingCardProps { paddle?: PaddleInstance; } -function PricingCard({ tier, paddlePrice, loading }: PricingCardProps): JSX.Element { +function PricingCard({ + tier, + paddlePrice, + loading, +}: PricingCardProps): JSX.Element { return (
(undefined); const { prices, loading } = usePaddlePrices(paddle, country || []); @@ -51,7 +58,6 @@ export function Pricing({ country }: PricingProps): JSX.Element { }).then((paddle) => { if (paddle) { setPaddle(paddle as unknown as PaddleInstance); - console.log("Paddle initialized"); } }); } @@ -62,9 +68,10 @@ export function Pricing({ country }: PricingProps): JSX.Element {
{PricingTier.map((tier) => { - const paddlePrice = tier.priceId && prices[tier.priceId] - ? prices[tier.priceId].replace(/\.00$/, "") - : null; + const paddlePrice = + tier.priceId && prices[tier.priceId] + ? prices[tier.priceId].replace(/\.00$/, "") + : null; return (

); -} \ No newline at end of file +} diff --git a/src/pages/checkout/success.tsx b/src/pages/checkout/success.tsx index 192b49614..8a8a68dce 100644 --- a/src/pages/checkout/success.tsx +++ b/src/pages/checkout/success.tsx @@ -12,7 +12,8 @@ function SuccessClient(): ReactNode { const [isLoading, setIsLoading] = useState(true); const [license, setLicense] = useState(null); const [error, setError] = useState(null); - const [transactionInfo, setTransactionInfo] = useState(null); + const [transactionInfo, setTransactionInfo] = + useState(null); const [copied, setCopied] = useState(false); const copyToClipboard = async (text: string): Promise => { @@ -31,7 +32,12 @@ function SuccessClient(): ReactNode { const transactionData: TransactionData = JSON.parse(storedData); setTransactionInfo(transactionData); } else { - setError(translate({ id: "checkout.success.noTransaction", message: "No transaction information found" })); + setError( + translate({ + id: "checkout.success.noTransaction", + message: "No transaction information found", + }), + ); setIsLoading(false); } }, []); @@ -45,11 +51,9 @@ function SuccessClient(): ReactNode { priceId: transactionInfo.priceId, }; - console.log("Generating license with data:", data); gateway .generateLicense(data) .then((result) => { - console.log("License API response:", result); if (result.success) { setLicense(result); } else { @@ -71,101 +75,151 @@ function SuccessClient(): ReactNode { <>

- Processing your payment... + + Processing your payment... +

- Please wait while we generate your license. + + Please wait while we generate your license. +

) : error ? ( <>
⚠️

- License Generation Failed + + License Generation Failed +

{error}

- Your payment was successful, but we encountered an issue generating your license. - Please contact support with your transaction ID. + Your payment was successful, but we encountered an issue + generating your license. Please contact support with your + transaction ID.

{transactionInfo?.transactionId && (

- Transaction ID + + Transaction ID +

- {transactionInfo.transactionId} + + {transactionInfo.transactionId} +
)} window.location.reload()}> - Try Again + + Try Again + ) : ( <>

- Payment Successful! + + Payment Successful! +

- Thank you for your purchase. Your order has been processed successfully. + Thank you for your purchase. Your order has been processed + successfully.

{license && (

- Your License + + Your License +

- License Key + + License Key +

- {license.license?.key} + + {license.license?.key} +
- Order ID: + + Order ID: + + + + {license.purchase?.id} - {license.purchase?.id}
- Email: + + Email: + + + + {license.customer?.email} - {license.customer?.email}
- Plan: + + Plan: + + + + {license.purchase?.planType} - {license.purchase?.planType}
- Type: + + Type: + + + + {license.purchase?.productType} - {license.purchase?.productType}
- Expires: + + Expires: + - {license.license?.expiredAt ? new Date(license.license.expiredAt).toLocaleDateString() : '-'} + {license.license?.expiredAt + ? new Date(license.license.expiredAt).toLocaleDateString() + : "-"}
@@ -178,7 +232,7 @@ function SuccessClient(): ReactNode {
)} - window.location.href = "/"}> + (window.location.href = "/")}> Return Home @@ -189,8 +243,22 @@ function SuccessClient(): ReactNode { export default function CheckoutSuccessPage(): ReactNode { return ( - - {translate({ id: "checkout.success.loading", message: "Loading..." })}
}> + + + {translate({ + id: "checkout.success.loading", + message: "Loading...", + })} +
+ } + > {() => } diff --git a/src/pages/orders.tsx b/src/pages/orders.tsx index 5215ffc0b..ce624cb29 100644 --- a/src/pages/orders.tsx +++ b/src/pages/orders.tsx @@ -88,12 +88,6 @@ function OrdersClient(): ReactNode { } }, [query.data, submittedEmail, fromCheckout]); - useEffect(() => { - if (userEmail !== "" && !hasSubmitted) return; - - window.history.replaceState({}, "", window.location.pathname); - }, [userEmail, hasSubmitted]); - const handleEmailSubmit = (e: React.FormEvent) => { e.preventDefault(); if (userEmail.trim()) { @@ -457,7 +451,10 @@ function OrdersClient(): ReactNode { })} setHasSubmitted(false)} + onClick={() => { + setUserEmail(""); + setHasSubmitted(false); + }} className="flex-1" > From dfcd25b89fe60f434accf8422f272492d70841f2 Mon Sep 17 00:00:00 2001 From: icyleaf Date: Thu, 12 Mar 2026 22:18:41 +0800 Subject: [PATCH 22/26] fix: add go back if fails to request checkout api --- src/pages/checkout/index.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx index 09b37af76..fcc3798d1 100644 --- a/src/pages/checkout/index.tsx +++ b/src/pages/checkout/index.tsx @@ -193,12 +193,20 @@ function CheckoutClient(): ReactNode { ) : ( <> {query.isError ? ( -
+

- Error loading your orders. + Error loading your checkout.

+ { + setHasSubmitted(false); + setSubmittedEmail(""); + }} + > + Go Back +
) : ( (query.isLoading || !query.data) && ( From 5de3b91a8b270528c5e47450c8e0eeac37346220 Mon Sep 17 00:00:00 2001 From: icyleaf Date: Tue, 17 Mar 2026 15:51:00 +0800 Subject: [PATCH 23/26] style: add animate loading orders --- src/pages/orders.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/orders.tsx b/src/pages/orders.tsx index ce624cb29..76d7a6ca5 100644 --- a/src/pages/orders.tsx +++ b/src/pages/orders.tsx @@ -154,7 +154,7 @@ function OrdersClient(): ReactNode {
) : ( (query.isLoading || !query.data) && ( -
+

From 3db0500dd64b38572aa45dded6cbd680f84d975c Mon Sep 17 00:00:00 2001 From: icyleaf Date: Tue, 17 Mar 2026 21:24:44 +0800 Subject: [PATCH 24/26] fix: order success result --- src/lib/gateway.ts | 8 +++----- src/lib/query.ts | 7 +++++++ src/pages/checkout/index.tsx | 27 ++++++++++++++++++--------- src/pages/checkout/success.tsx | 16 +++++++++------- src/pages/orders.tsx | 2 +- src/types/index.ts | 26 +++++++++++++++++++++----- 6 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/lib/gateway.ts b/src/lib/gateway.ts index d62ae0222..ff3a352a8 100644 --- a/src/lib/gateway.ts +++ b/src/lib/gateway.ts @@ -25,14 +25,12 @@ class Gateway implements GatewayClient { }); if (!response.ok) { - if (response.status === 404) { - return { orders: [] }; - } - const errorText = await response.text(); - throw new Error( + const error = new Error( `Gateway API request failed: ${response.status} ${response.statusText} - ${errorText}` ); + (error as any).status = response.status; + throw error; } return await response.json() as OrdersResponse; diff --git a/src/lib/query.ts b/src/lib/query.ts index c7c2d2157..41ef880c8 100644 --- a/src/lib/query.ts +++ b/src/lib/query.ts @@ -34,5 +34,12 @@ export const useCustomerOrders = ( return gateway.orders(encryptedEmail); }, enabled: !!email && !!gateway && enabled, + retry: (failureCount, error) => { + // 404 错误不重试,其他错误重试 2 次 + if ((error as any)?.status === 404) { + return false; + } + return failureCount < 2; + }, }); }; \ No newline at end of file diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx index fcc3798d1..76ac5e9db 100644 --- a/src/pages/checkout/index.tsx +++ b/src/pages/checkout/index.tsx @@ -16,6 +16,11 @@ import type { PaddleCheckoutOpenOptions, } from "@site/src/types"; +// 判断错误是否是 404 +function isNotFoundError(error: Error | null): boolean { + return (error as any)?.status === 404; +} + function CheckoutClient(): ReactNode { const { i18n } = useDocusaurusContext(); const history = useHistory(); @@ -79,7 +84,7 @@ function CheckoutClient(): ReactNode { break; case "checkout.closed": - console.log("Checkout closed"); + console.log("Checkout closed or cancelled", data); history.push(`${basePath}/pricing`); break; @@ -123,13 +128,14 @@ function CheckoutClient(): ReactNode { } return; } + console.log("Error", query.error); + + // 没有订单或 404 错误时,允许继续结账;其他错误不打开 checkout + const shouldOpenCheckout = + (query.data && query.data.orders.length === 0) || + (query.isError && isNotFoundError(query.error)); - if ( - query.data && - query.data.orders.length === 0 && - paddle?.Initialized && - priceId - ) { + if (shouldOpenCheckout && paddle?.Initialized && priceId) { const name = submittedEmail.split("@")[0] || ""; const options: PaddleCheckoutOpenOptions = { customer: { email: submittedEmail, name, address: { countryCode } }, @@ -151,6 +157,7 @@ function CheckoutClient(): ReactNode { } }, [ query.data, + query.isError, paddle, priceId, quantity, @@ -192,7 +199,7 @@ function CheckoutClient(): ReactNode { ) : ( <> - {query.isError ? ( + {query.isError && !isNotFoundError(query.error) ? (

@@ -209,7 +216,9 @@ function CheckoutClient(): ReactNode {

) : ( - (query.isLoading || !query.data) && ( + (query.isLoading || + !query.data || + (query.isError && isNotFoundError(query.error))) && (

diff --git a/src/pages/checkout/success.tsx b/src/pages/checkout/success.tsx index 8a8a68dce..69fbca743 100644 --- a/src/pages/checkout/success.tsx +++ b/src/pages/checkout/success.tsx @@ -177,7 +177,7 @@ function SuccessClient(): ReactNode { - {license.purchase?.id} + {license.order?.id}

@@ -192,22 +192,24 @@ function SuccessClient(): ReactNode {
- - Plan: + + Amount: - {license.purchase?.planType} + {license.order + ? `$${(license.order.totalAmount / 100).toFixed(2)} ${license.order.currency}` + : "-"}
- - Type: + + Status: - {license.purchase?.productType} + {license.license?.active ? "Active" : "Inactive"}
diff --git a/src/pages/orders.tsx b/src/pages/orders.tsx index 76d7a6ca5..2c9e9960c 100644 --- a/src/pages/orders.tsx +++ b/src/pages/orders.tsx @@ -179,7 +179,7 @@ function OrdersClient(): ReactNode {
{fromCheckout && query.data.orders && - query.data.orders.length > 0 && ( + query.data.orders?.length > 0 && (

diff --git a/src/types/index.ts b/src/types/index.ts index 990299057..1c8423382 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -49,22 +49,38 @@ export interface LicenseRequest { } export interface LicenseData { + id: number; key: string; expiredAt: string; + active: boolean; +} + +export interface OrderData { + id: string; + totalAmount: number; + currency: string; + status: string; } -export interface PurchaseData { +export interface SubscriptionData { id: string; - planType: string; - productType: string; + status: string; + currentPeriodEnd: string; +} + +export interface LicenseCustomer { + id: string; + email: string; + paddleCustomerId: string; } export interface LicenseResponse { success: boolean; message?: string; license?: LicenseData; - purchase?: PurchaseData; - customer?: Customer; + order?: OrderData; + subscription?: SubscriptionData; + customer?: LicenseCustomer; } // ===== Paddle 相关类型 ===== From 9dc1935696c03206ae3d95d13e2ecb587ec5db0e Mon Sep 17 00:00:00 2001 From: icyleaf Date: Wed, 18 Mar 2026 08:50:11 +0800 Subject: [PATCH 25/26] fix: remove email query after click search other order --- src/pages/orders.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/orders.tsx b/src/pages/orders.tsx index 2c9e9960c..b5f4c1730 100644 --- a/src/pages/orders.tsx +++ b/src/pages/orders.tsx @@ -205,7 +205,11 @@ function OrdersClient(): ReactNode { setHasSubmitted(false)} + onClick={() => { + setUserEmail(""); + setHasSubmitted(false); + window.history.replaceState({}, "", window.location.pathname); + }} className="flex-1" > @@ -454,6 +458,7 @@ function OrdersClient(): ReactNode { onClick={() => { setUserEmail(""); setHasSubmitted(false); + window.history.replaceState({}, "", window.location.pathname); }} className="flex-1" > From 099af81542d1bc36193aea3154978eb7da519785 Mon Sep 17 00:00:00 2001 From: icyleaf Date: Wed, 18 Mar 2026 11:58:41 +0800 Subject: [PATCH 26/26] fix: show no orders information --- src/lib/gateway.ts | 70 ++++++++++++++++++------------------------ src/lib/query.ts | 9 +++--- src/pages/orders.tsx | 72 +++++++++++++++++++++++++++++++++++--------- 3 files changed, 93 insertions(+), 58 deletions(-) diff --git a/src/lib/gateway.ts b/src/lib/gateway.ts index ff3a352a8..389c27bae 100644 --- a/src/lib/gateway.ts +++ b/src/lib/gateway.ts @@ -1,6 +1,11 @@ -import type { GatewayClient, OrdersResponse, LicenseRequest, LicenseResponse } from "@site/src/types"; +import type { + GatewayClient, + OrdersResponse, + LicenseRequest, + LicenseResponse, +} from "@site/src/types"; -class Gateway implements GatewayClient { +export default class Gateway implements GatewayClient { private baseURL: string; constructor(baseURL: string) { @@ -16,28 +21,14 @@ class Gateway implements GatewayClient { } const url = `${this.baseURL}/payments/orders?email=${encodeURIComponent( - email + email, )}`; - try { - const response = await fetch(url, { - method: "GET", - }); + const response = await fetch(url, { + method: "GET", + }); - if (!response.ok) { - const errorText = await response.text(); - const error = new Error( - `Gateway API request failed: ${response.status} ${response.statusText} - ${errorText}` - ); - (error as any).status = response.status; - throw error; - } - - return await response.json() as OrdersResponse; - } catch (error) { - console.error("Failed to fetch orders from gateway:", error); - throw error; - } + return this.handleResponse(response); } async generateLicense(data: LicenseRequest): Promise { @@ -47,32 +38,31 @@ class Gateway implements GatewayClient { const url = `${this.baseURL}/payments/license`; - try { - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }); + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); - if (!response.ok) { - const errorText = await response.text(); - throw new Error( - `Failed to generate license: ${response.status} ${response.statusText} - ${errorText}` - ); - } + return this.handleResponse(response); + } - return await response.json() as LicenseResponse; - } catch (error) { - console.error("Failed to generate license:", error); + async handleResponse(response: Response): Promise { + const body = await response.json(); + console.log("Gateway response:", body); + if (!response.ok) { + const error = new Error(body.message || `Request failed with status ${response.status}`); + (error as any).status = response.status; + (error as any).data = body; throw error; } + + return body; } } export function initGateway(baseURL: string): GatewayClient { return new Gateway(baseURL); } - -export default Gateway; \ No newline at end of file diff --git a/src/lib/query.ts b/src/lib/query.ts index 41ef880c8..9cd4317d3 100644 --- a/src/lib/query.ts +++ b/src/lib/query.ts @@ -2,10 +2,11 @@ import { useQuery, type UseQueryResult } from "@tanstack/react-query"; import { encrypt } from "@site/src/lib/crypto"; import type { GatewayClient, OrdersResponse } from "@site/src/types"; -const secretKey = process.env.ZEALOT_ENCRYPTION_KEY || 'default-key-for-ssr-build'; +const secretKey = + process.env.ZEALOT_ENCRYPTION_KEY || "default-key-for-ssr-build"; function getSecretKey(): string { - if (typeof window !== 'undefined' && secretKey === 'default-key-for-ssr-build') { + if (secretKey === "default-key-for-ssr-build") { throw new Error("ZEALOT_ENCRYPTION_KEY is not defined"); } return secretKey; @@ -21,7 +22,7 @@ function getSecretKey(): string { export const useCustomerOrders = ( gateway: GatewayClient | undefined, email: string, - enabled = false + enabled = false, ): UseQueryResult => { return useQuery({ queryKey: ["customerOrders", email], @@ -42,4 +43,4 @@ export const useCustomerOrders = ( return failureCount < 2; }, }); -}; \ No newline at end of file +}; diff --git a/src/pages/orders.tsx b/src/pages/orders.tsx index b5f4c1730..f1e5b15a9 100644 --- a/src/pages/orders.tsx +++ b/src/pages/orders.tsx @@ -140,18 +140,54 @@ function OrdersClient(): ReactNode { ) : ( <> {query.isError ? ( -

-
-

- - Error loading your orders. - -

+ (query.error as any)?.status === 404 || + (query.error as Error)?.message?.includes("404") ? ( +
+
+

+ + No orders found for this email. + +

+
+
+ + + Back to Pricing + + + { + setUserEmail(""); + setHasSubmitted(false); + window.history.replaceState( + {}, + "", + window.location.pathname, + ); + }} + className="flex-1" + > + + Search Another Email + + +
- setHasSubmitted(false)}> - Try Again - -
+ ) : ( +
+
+

+ + Error loading your orders. + +

+
+ setHasSubmitted(false)}> + Try Again + +
+ ) ) : ( (query.isLoading || !query.data) && (
@@ -192,7 +228,7 @@ function OrdersClient(): ReactNode { {query.data.orders?.length === 0 ? (
-

+

No orders found for this email. @@ -208,7 +244,11 @@ function OrdersClient(): ReactNode { onClick={() => { setUserEmail(""); setHasSubmitted(false); - window.history.replaceState({}, "", window.location.pathname); + window.history.replaceState( + {}, + "", + window.location.pathname, + ); }} className="flex-1" > @@ -458,7 +498,11 @@ function OrdersClient(): ReactNode { onClick={() => { setUserEmail(""); setHasSubmitted(false); - window.history.replaceState({}, "", window.location.pathname); + window.history.replaceState( + {}, + "", + window.location.pathname, + ); }} className="flex-1" >