@windrun-huaiin/third-ui 11.1.0 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/clerk/clerk-optional-auth.d.ts +12 -0
  2. package/dist/clerk/clerk-optional-auth.js +33 -0
  3. package/dist/clerk/clerk-optional-auth.mjs +31 -0
  4. package/dist/clerk/clerk-provider-client.d.ts +3 -1
  5. package/dist/clerk/clerk-provider-client.js +22 -12
  6. package/dist/clerk/clerk-provider-client.mjs +22 -12
  7. package/dist/clerk/optional-auth.d.ts +12 -0
  8. package/dist/clerk/optional-auth.js +47 -0
  9. package/dist/clerk/optional-auth.mjs +45 -0
  10. package/dist/clerk/patch/optional-auth.d.ts +11 -0
  11. package/dist/clerk/patch/optional-auth.js +27 -0
  12. package/dist/clerk/patch/optional-auth.mjs +25 -0
  13. package/dist/fuma/base/custom-home-layout.d.ts +9 -1
  14. package/dist/fuma/base/custom-home-layout.js +2 -2
  15. package/dist/fuma/base/custom-home-layout.mjs +2 -2
  16. package/dist/fuma/fuma-page-genarator.d.ts +3 -1
  17. package/dist/fuma/fuma-page-genarator.js +8 -3
  18. package/dist/fuma/fuma-page-genarator.mjs +8 -3
  19. package/dist/lib/seo-util.d.ts +6 -2
  20. package/dist/lib/seo-util.js +21 -11
  21. package/dist/lib/seo-util.mjs +21 -11
  22. package/dist/main/credit/credit-overview-client.js +2 -2
  23. package/dist/main/credit/credit-overview-client.mjs +2 -2
  24. package/dist/main/footer-email.js +1 -1
  25. package/dist/main/footer-email.mjs +1 -1
  26. package/dist/main/footer.d.ts +6 -2
  27. package/dist/main/footer.js +3 -2
  28. package/dist/main/footer.mjs +3 -2
  29. package/package.json +8 -3
  30. package/src/clerk/clerk-provider-client.tsx +37 -12
  31. package/src/clerk/patch/optional-auth.ts +24 -0
  32. package/src/fuma/base/custom-home-layout.tsx +11 -1
  33. package/src/fuma/fuma-page-genarator.tsx +19 -4
  34. package/src/lib/seo-util.ts +27 -13
  35. package/src/main/credit/credit-overview-client.tsx +4 -4
  36. package/src/main/footer-email.tsx +1 -1
  37. package/src/main/footer.tsx +10 -3
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import { getAsNeededLocalizedUrl } from '@windrun-huaiin/lib';
3
4
 
4
5
  /**
5
6
  * Generate robots.txt content
@@ -21,9 +22,11 @@ function generateRobots(baseUrl) {
21
22
  * @param locales - Supported locales array
22
23
  * @param mdxSourceDir - MDX source directory path
23
24
  * @param openMdxSEOSiteMap - Whether to include MDX content in sitemap, default is true
25
+ * @param localPrefixAsNeeded - Whether localePrefix is set to 'as-needed' (default: true)
26
+ * @param defaultLocale - The default locale for the application (default: 'en')
24
27
  * @returns Sitemap entries
25
28
  */
26
- function generateSitemap(baseUrl, locales, mdxSourceDir, openMdxSEOSiteMap = true) {
29
+ function generateSitemap(baseUrl, locales, mdxSourceDir, openMdxSEOSiteMap = true, localPrefixAsNeeded = true, defaultLocale = 'en') {
27
30
  // 2. handle index.mdx (blog start page) and other slugs
28
31
  const blogRoutes = [];
29
32
  // 1. read all blog mdx file names with error handling
@@ -36,8 +39,9 @@ function generateSitemap(baseUrl, locales, mdxSourceDir, openMdxSEOSiteMap = tru
36
39
  for (const locale of locales) {
37
40
  for (const f of blogFiles) {
38
41
  if (f === 'index.mdx') {
42
+ const localizedPath = getAsNeededLocalizedUrl(locale, '/blog', localPrefixAsNeeded, defaultLocale);
39
43
  blogRoutes.push({
40
- url: `${baseUrl}/${locale}/blog`,
44
+ url: `${baseUrl}${localizedPath}`,
41
45
  lastModified: new Date(),
42
46
  changeFrequency: 'daily',
43
47
  priority: 1.0
@@ -45,8 +49,9 @@ function generateSitemap(baseUrl, locales, mdxSourceDir, openMdxSEOSiteMap = tru
45
49
  }
46
50
  else {
47
51
  const slug = f.replace(/\.mdx$/, '');
52
+ const localizedPath = getAsNeededLocalizedUrl(locale, `/blog/${slug}`, localPrefixAsNeeded, defaultLocale);
48
53
  blogRoutes.push({
49
- url: `${baseUrl}/${locale}/blog/${slug}`,
54
+ url: `${baseUrl}${localizedPath}`,
50
55
  lastModified: new Date(),
51
56
  changeFrequency: f === 'ioc.mdx' ? 'daily' : 'monthly',
52
57
  priority: 0.8
@@ -62,12 +67,15 @@ function generateSitemap(baseUrl, locales, mdxSourceDir, openMdxSEOSiteMap = tru
62
67
  }
63
68
  }
64
69
  // 3. main page (all language versions)
65
- const mainRoutes = locales.map(locale => ({
66
- url: `${baseUrl}/${locale}`,
67
- lastModified: new Date(),
68
- changeFrequency: 'weekly',
69
- priority: 1.0
70
- }));
70
+ const mainRoutes = locales.map(locale => {
71
+ const localizedPath = getAsNeededLocalizedUrl(locale, '/', localPrefixAsNeeded, defaultLocale);
72
+ return {
73
+ url: `${baseUrl}${localizedPath}`,
74
+ lastModified: new Date(),
75
+ changeFrequency: 'weekly',
76
+ priority: 1.0
77
+ };
78
+ });
71
79
  return openMdxSEOSiteMap ? [...mainRoutes, ...blogRoutes] : [...mainRoutes];
72
80
  }
73
81
  /**
@@ -86,12 +94,14 @@ function createRobotsHandler(baseUrl) {
86
94
  * @param locales - Supported locales array
87
95
  * @param mdxSourceDir - MDX source directory path, default is empty
88
96
  * @param openMdxSEOSiteMap - Whether to include MDX content in sitemap, default is true
97
+ * @param localPrefixAsNeeded - Whether localePrefix is set to 'as-needed' (default: true)
98
+ * @param defaultLocale - The default locale for the application (default: 'en')
89
99
  * @returns Sitemap handler function
90
100
  */
91
- function createSitemapHandler(baseUrl, locales, mdxSourceDir = '', openMdxSEOSiteMap = true) {
101
+ function createSitemapHandler(baseUrl, locales, mdxSourceDir = '', openMdxSEOSiteMap = true, localPrefixAsNeeded = true, defaultLocale = 'en') {
92
102
  // force static generation
93
103
  const sitemapHandler = function sitemap() {
94
- return generateSitemap(baseUrl, locales, mdxSourceDir, openMdxSEOSiteMap);
104
+ return generateSitemap(baseUrl, locales, mdxSourceDir, openMdxSEOSiteMap, localPrefixAsNeeded, defaultLocale);
95
105
  };
96
106
  // Add static generation directive
97
107
  sitemapHandler.dynamic = 'force-static';
@@ -231,9 +231,9 @@ function CreditOverviewClient({ data, locale, translations, className, expiringS
231
231
  userToggledRef.current = true;
232
232
  setBucketExpanded(true);
233
233
  }, []);
234
- return (jsxRuntime.jsxs("section", { className: utils.cn("flex flex-col gap-2 p-2 shadow-inner rounded-xl bg-white dark:bg-slate-900", className), children: [jsxRuntime.jsxs("header", { className: "relative rounded-2xl bg-linear-to-bl from-indigo-200/60 via-indigo-400/90 to-purple-200/50 p-4 shadow-inner dark:from-indigo-300/20 dark:via-slate-400 dark:to-slate-500/50 sm:p-6", children: [jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 sm:gap-3", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-start rounded-full ", children: [jsxRuntime.jsx(server.globalLucideIcons.Gem, { "aria-hidden": true, className: "mr-2 h-6 w-6 sm:h-8 sm:w-8" }), jsxRuntime.jsx("span", { className: "text-sm font-medium sm:text-base", children: translations.totalLabel })] }), jsxRuntime.jsx("div", { className: "flex justify-center text-3xl font-semibold leading-tight sm:text-4xl", children: formatNumber(locale, data.totalBalance) }), jsxRuntime.jsxs("div", { className: "flex-1 flex-col gap-1", children: [jsxRuntime.jsx("p", { className: "text-xs text-gray-700 dark:text-slate-100 sm:text-sm", children: translations.subscriptionPeriodLabel }), jsxRuntime.jsx("h4", { className: "text-xl font-semibold sm:text-2xl", children: subscription ? subscription.planName : translations.subscriptionInactive })] }), jsxRuntime.jsx("div", { className: "pt-2 sm:pt-0", children: jsxRuntime.jsx(gradientButton.GradientButton, { title: subscription ? translations.subscriptionManage : translations.subscribePay, align: "center", icon: subscription ? jsxRuntime.jsx(server.globalLucideIcons.Settings2, {}) : jsxRuntime.jsx(server.globalLucideIcons.Bell, {}), openInNewTab: false, className: "w-full", onClick: subscription ? handleManageAction : handleSubscribeAction }) })] }), jsxRuntime.jsx("div", { className: "absolute right-3 top-3 sm:right-6 sm:top-6", children: jsxRuntime.jsx(HoverInfo, { label: translations.totalLabel, description: translations.summaryDescription }) })] }), jsxRuntime.jsxs("section", { className: "relative flex flex-col gap-3 rounded-2xl border p-4 shadow-inner sm:gap-2 sm:p-5", children: [jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [jsxRuntime.jsx("h3", { className: "text-base font-semibold text-gray-500 dark:text-slate-100 sm:text-lg", children: translations.bucketsTitle }), hasBuckets ? (jsxRuntime.jsx("button", { type: "button", "aria-expanded": bucketExpanded, "aria-label": bucketExpanded ? translations.hiddenDetail : translations.expandDetail, onClick: toggleBucketExpanded, className: "flex h-7 w-7 items-center justify-center rounded-full border border-transparent bg-white text-purple-600 shadow-[0_6px_20px_rgba(99,102,241,0.25)] transition-colors hover:text-purple-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-purple-500 dark:bg-[#1b1541] dark:text-purple-100 dark:hover:text-purple-50 dark:shadow-[0_6px_22px_rgba(112,86,255,0.35)]", children: bucketExpanded ? (jsxRuntime.jsx(server.globalLucideIcons.ChevronUp, { className: "h-4 w-4" })) : (jsxRuntime.jsx(server.globalLucideIcons.ChevronDown, { className: "h-4 w-4" })) })) : null] }), hasBuckets ? (bucketExpanded ? (jsxRuntime.jsx("ul", { className: "flex flex-col gap-2", children: buckets.map((bucket) => {
234
+ return (jsxRuntime.jsxs("section", { className: utils.cn("flex flex-col gap-2 p-2 shadow-inner rounded-xl bg-white dark:bg-slate-900", className), children: [jsxRuntime.jsxs("header", { className: "relative rounded-2xl bg-linear-to-bl from-indigo-200/60 via-indigo-400/90 to-purple-200/50 p-4 shadow-inner dark:from-indigo-300/20 dark:via-slate-400 dark:to-slate-500/50 sm:p-6", children: [jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 sm:gap-3", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-start rounded-full ", children: [jsxRuntime.jsx(server.globalLucideIcons.Gem, { "aria-hidden": true, className: "mr-2 h-6 w-6 sm:h-8 sm:w-8" }), jsxRuntime.jsx("span", { className: "text-base sm:text-lg", children: translations.totalLabel })] }), jsxRuntime.jsx("div", { className: "flex justify-center text-3xl font-semibold leading-tight sm:text-4xl", children: formatNumber(locale, data.totalBalance) }), jsxRuntime.jsxs("div", { className: "flex-1 flex-col gap-1", children: [jsxRuntime.jsx("p", { className: "text-xs text-gray-700 dark:text-slate-100 sm:text-sm", children: translations.subscriptionPeriodLabel }), jsxRuntime.jsx("h4", { className: "text-xl font-semibold sm:text-2xl", children: subscription ? subscription.planName : translations.subscriptionInactive })] }), jsxRuntime.jsx("div", { className: "pt-2 sm:pt-0", children: jsxRuntime.jsx(gradientButton.GradientButton, { title: subscription ? translations.subscriptionManage : translations.subscribePay, align: "center", icon: subscription ? jsxRuntime.jsx(server.globalLucideIcons.Settings2, {}) : jsxRuntime.jsx(server.globalLucideIcons.Bell, {}), openInNewTab: false, className: "w-full", onClick: subscription ? handleManageAction : handleSubscribeAction }) })] }), jsxRuntime.jsx("div", { className: "absolute right-3 top-3 sm:right-6 sm:top-6", children: jsxRuntime.jsx(HoverInfo, { label: translations.totalLabel, description: translations.summaryDescription }) })] }), jsxRuntime.jsxs("section", { className: "relative flex flex-col gap-3 rounded-2xl border p-4 shadow-inner sm:gap-2 sm:p-5", children: [jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [jsxRuntime.jsx("h3", { className: "text-base text-gray-500 dark:text-slate-100 sm:text-lg", children: translations.bucketsTitle }), hasBuckets ? (jsxRuntime.jsx("button", { type: "button", "aria-expanded": bucketExpanded, "aria-label": bucketExpanded ? translations.hiddenDetail : translations.expandDetail, onClick: toggleBucketExpanded, className: "flex h-7 w-7 items-center justify-center rounded-full border border-transparent bg-white text-purple-600 shadow-[0_6px_20px_rgba(99,102,241,0.25)] transition-colors hover:text-purple-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-purple-500 dark:bg-[#1b1541] dark:text-purple-100 dark:hover:text-purple-50 dark:shadow-[0_6px_22px_rgba(112,86,255,0.35)]", children: bucketExpanded ? (jsxRuntime.jsx(server.globalLucideIcons.ChevronUp, { className: "h-4 w-4" })) : (jsxRuntime.jsx(server.globalLucideIcons.ChevronDown, { className: "h-4 w-4" })) })) : null] }), hasBuckets ? (bucketExpanded ? (jsxRuntime.jsx("ul", { className: "flex flex-col gap-2", children: buckets.map((bucket) => {
235
235
  const balanceDisplay = formatNumber(locale, bucket.balance);
236
- return (jsxRuntime.jsxs("li", { "data-credit-kind": bucket.kind, className: "rounded-2xl border border-slate-200/70 bg-white/85 px-3 py-3 text-sm shadow-sm transition-transform hover:-translate-y-0.5 hover:shadow-md dark:border-slate-800/60 dark:bg-slate-900/60 sm:px-4", children: [jsxRuntime.jsxs("div", { className: "grid grid-cols-[1fr_auto] items-center gap-3 text-xs sm:text-sm", children: [jsxRuntime.jsx("span", { className: "flex min-w-0 items-center gap-2", children: jsxRuntime.jsx("span", { className: "max-w-full truncate rounded-full bg-purple-50 px-2 py-1 text-xs font-semibold text-purple-600 shadow-sm dark:bg-purple-500/20 dark:text-purple-100 sm:text-sm", children: bucket.computedLabel }) }), jsxRuntime.jsx("span", { className: "flex min-w-0 justify-end", children: jsxRuntime.jsx("span", { className: "text-right text-base font-semibold leading-tight text-gray-500 dark:text-slate-100 sm:text-lg", title: balanceDisplay, children: balanceDisplay }) })] }), jsxRuntime.jsx("div", { className: "mt-3 flex justify-end gap-2", children: jsxRuntime.jsxs("span", { className: "text-[11px] font-semibold text-gray-500 dark:text-slate-100 sm:text-xs", children: [translations.expiredAtLabel, ": ", bucket.expiresAt] }) })] }, bucket.kind));
236
+ return (jsxRuntime.jsxs("li", { "data-credit-kind": bucket.kind, className: "rounded-2xl border border-slate-200/70 bg-white/85 px-3 py-3 text-sm shadow-sm transition-transform hover:-translate-y-0.5 hover:shadow-md dark:border-slate-800/60 dark:bg-slate-900/60 sm:px-4", children: [jsxRuntime.jsxs("div", { className: "grid grid-cols-[1fr_auto] items-center gap-3 text-xs sm:text-sm", children: [jsxRuntime.jsx("span", { className: "flex min-w-0 items-center gap-2", children: jsxRuntime.jsx("span", { className: "max-w-full truncate rounded-full bg-purple-50 px-2 py-1 text-xs text-purple-600 shadow-sm dark:bg-purple-500/20 dark:text-purple-100 sm:text-sm", children: bucket.computedLabel }) }), jsxRuntime.jsx("span", { className: "flex min-w-0 justify-end", children: jsxRuntime.jsx("span", { className: "text-right text-base font-semibold leading-tight text-gray-500 dark:text-slate-100 sm:text-lg", title: balanceDisplay, children: balanceDisplay }) })] }), jsxRuntime.jsx("div", { className: "mt-3 flex justify-end gap-2", children: jsxRuntime.jsxs("span", { className: "text-[11px] text-gray-500 dark:text-slate-100 sm:text-xs", children: [translations.expiredAtLabel, ": ", bucket.expiresAt] }) })] }, bucket.kind));
237
237
  }) })) : (jsxRuntime.jsx("button", { type: "button", onClick: expandBuckets, className: "w-full rounded-2xl border border-slate-200/70 bg-white/85 p-6 sm:px-4 text-sm shadow-sm transition-transform hover:-translate-y-0.5 hover:shadow-md dark:border-slate-800/60 dark:bg-slate-900/60 hover:text-purple-500", children: translations.expandDetail }))) : (jsxRuntime.jsx("div", { className: "w-full rounded-2xl border border-slate-200/70 bg-white/85 p-6 sm:px-4 text-sm shadow-sm transition-transform dark:border-slate-800/60 dark:bg-slate-900/60 text-center", children: translations.bucketsEmpty })), jsxRuntime.jsx(gradientButton.GradientButton, { title: translations.onetimeBuy, icon: jsxRuntime.jsx(server.globalLucideIcons.ShoppingCart, {}), align: "center", className: "w-full text-sm sm:text-base", onClick: handleOnetimeAction })] })] }));
238
238
  }
239
239
  function deriveStatus(expiresAt, thresholdDays = 7) {
@@ -229,9 +229,9 @@ function CreditOverviewClient({ data, locale, translations, className, expiringS
229
229
  userToggledRef.current = true;
230
230
  setBucketExpanded(true);
231
231
  }, []);
232
- return (jsxs("section", { className: cn("flex flex-col gap-2 p-2 shadow-inner rounded-xl bg-white dark:bg-slate-900", className), children: [jsxs("header", { className: "relative rounded-2xl bg-linear-to-bl from-indigo-200/60 via-indigo-400/90 to-purple-200/50 p-4 shadow-inner dark:from-indigo-300/20 dark:via-slate-400 dark:to-slate-500/50 sm:p-6", children: [jsxs("div", { className: "flex flex-col gap-2 sm:gap-3", children: [jsxs("div", { className: "flex items-center justify-start rounded-full ", children: [jsx(globalLucideIcons.Gem, { "aria-hidden": true, className: "mr-2 h-6 w-6 sm:h-8 sm:w-8" }), jsx("span", { className: "text-sm font-medium sm:text-base", children: translations.totalLabel })] }), jsx("div", { className: "flex justify-center text-3xl font-semibold leading-tight sm:text-4xl", children: formatNumber(locale, data.totalBalance) }), jsxs("div", { className: "flex-1 flex-col gap-1", children: [jsx("p", { className: "text-xs text-gray-700 dark:text-slate-100 sm:text-sm", children: translations.subscriptionPeriodLabel }), jsx("h4", { className: "text-xl font-semibold sm:text-2xl", children: subscription ? subscription.planName : translations.subscriptionInactive })] }), jsx("div", { className: "pt-2 sm:pt-0", children: jsx(GradientButton, { title: subscription ? translations.subscriptionManage : translations.subscribePay, align: "center", icon: subscription ? jsx(globalLucideIcons.Settings2, {}) : jsx(globalLucideIcons.Bell, {}), openInNewTab: false, className: "w-full", onClick: subscription ? handleManageAction : handleSubscribeAction }) })] }), jsx("div", { className: "absolute right-3 top-3 sm:right-6 sm:top-6", children: jsx(HoverInfo, { label: translations.totalLabel, description: translations.summaryDescription }) })] }), jsxs("section", { className: "relative flex flex-col gap-3 rounded-2xl border p-4 shadow-inner sm:gap-2 sm:p-5", children: [jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [jsx("h3", { className: "text-base font-semibold text-gray-500 dark:text-slate-100 sm:text-lg", children: translations.bucketsTitle }), hasBuckets ? (jsx("button", { type: "button", "aria-expanded": bucketExpanded, "aria-label": bucketExpanded ? translations.hiddenDetail : translations.expandDetail, onClick: toggleBucketExpanded, className: "flex h-7 w-7 items-center justify-center rounded-full border border-transparent bg-white text-purple-600 shadow-[0_6px_20px_rgba(99,102,241,0.25)] transition-colors hover:text-purple-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-purple-500 dark:bg-[#1b1541] dark:text-purple-100 dark:hover:text-purple-50 dark:shadow-[0_6px_22px_rgba(112,86,255,0.35)]", children: bucketExpanded ? (jsx(globalLucideIcons.ChevronUp, { className: "h-4 w-4" })) : (jsx(globalLucideIcons.ChevronDown, { className: "h-4 w-4" })) })) : null] }), hasBuckets ? (bucketExpanded ? (jsx("ul", { className: "flex flex-col gap-2", children: buckets.map((bucket) => {
232
+ return (jsxs("section", { className: cn("flex flex-col gap-2 p-2 shadow-inner rounded-xl bg-white dark:bg-slate-900", className), children: [jsxs("header", { className: "relative rounded-2xl bg-linear-to-bl from-indigo-200/60 via-indigo-400/90 to-purple-200/50 p-4 shadow-inner dark:from-indigo-300/20 dark:via-slate-400 dark:to-slate-500/50 sm:p-6", children: [jsxs("div", { className: "flex flex-col gap-2 sm:gap-3", children: [jsxs("div", { className: "flex items-center justify-start rounded-full ", children: [jsx(globalLucideIcons.Gem, { "aria-hidden": true, className: "mr-2 h-6 w-6 sm:h-8 sm:w-8" }), jsx("span", { className: "text-base sm:text-lg", children: translations.totalLabel })] }), jsx("div", { className: "flex justify-center text-3xl font-semibold leading-tight sm:text-4xl", children: formatNumber(locale, data.totalBalance) }), jsxs("div", { className: "flex-1 flex-col gap-1", children: [jsx("p", { className: "text-xs text-gray-700 dark:text-slate-100 sm:text-sm", children: translations.subscriptionPeriodLabel }), jsx("h4", { className: "text-xl font-semibold sm:text-2xl", children: subscription ? subscription.planName : translations.subscriptionInactive })] }), jsx("div", { className: "pt-2 sm:pt-0", children: jsx(GradientButton, { title: subscription ? translations.subscriptionManage : translations.subscribePay, align: "center", icon: subscription ? jsx(globalLucideIcons.Settings2, {}) : jsx(globalLucideIcons.Bell, {}), openInNewTab: false, className: "w-full", onClick: subscription ? handleManageAction : handleSubscribeAction }) })] }), jsx("div", { className: "absolute right-3 top-3 sm:right-6 sm:top-6", children: jsx(HoverInfo, { label: translations.totalLabel, description: translations.summaryDescription }) })] }), jsxs("section", { className: "relative flex flex-col gap-3 rounded-2xl border p-4 shadow-inner sm:gap-2 sm:p-5", children: [jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [jsx("h3", { className: "text-base text-gray-500 dark:text-slate-100 sm:text-lg", children: translations.bucketsTitle }), hasBuckets ? (jsx("button", { type: "button", "aria-expanded": bucketExpanded, "aria-label": bucketExpanded ? translations.hiddenDetail : translations.expandDetail, onClick: toggleBucketExpanded, className: "flex h-7 w-7 items-center justify-center rounded-full border border-transparent bg-white text-purple-600 shadow-[0_6px_20px_rgba(99,102,241,0.25)] transition-colors hover:text-purple-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-purple-500 dark:bg-[#1b1541] dark:text-purple-100 dark:hover:text-purple-50 dark:shadow-[0_6px_22px_rgba(112,86,255,0.35)]", children: bucketExpanded ? (jsx(globalLucideIcons.ChevronUp, { className: "h-4 w-4" })) : (jsx(globalLucideIcons.ChevronDown, { className: "h-4 w-4" })) })) : null] }), hasBuckets ? (bucketExpanded ? (jsx("ul", { className: "flex flex-col gap-2", children: buckets.map((bucket) => {
233
233
  const balanceDisplay = formatNumber(locale, bucket.balance);
234
- return (jsxs("li", { "data-credit-kind": bucket.kind, className: "rounded-2xl border border-slate-200/70 bg-white/85 px-3 py-3 text-sm shadow-sm transition-transform hover:-translate-y-0.5 hover:shadow-md dark:border-slate-800/60 dark:bg-slate-900/60 sm:px-4", children: [jsxs("div", { className: "grid grid-cols-[1fr_auto] items-center gap-3 text-xs sm:text-sm", children: [jsx("span", { className: "flex min-w-0 items-center gap-2", children: jsx("span", { className: "max-w-full truncate rounded-full bg-purple-50 px-2 py-1 text-xs font-semibold text-purple-600 shadow-sm dark:bg-purple-500/20 dark:text-purple-100 sm:text-sm", children: bucket.computedLabel }) }), jsx("span", { className: "flex min-w-0 justify-end", children: jsx("span", { className: "text-right text-base font-semibold leading-tight text-gray-500 dark:text-slate-100 sm:text-lg", title: balanceDisplay, children: balanceDisplay }) })] }), jsx("div", { className: "mt-3 flex justify-end gap-2", children: jsxs("span", { className: "text-[11px] font-semibold text-gray-500 dark:text-slate-100 sm:text-xs", children: [translations.expiredAtLabel, ": ", bucket.expiresAt] }) })] }, bucket.kind));
234
+ return (jsxs("li", { "data-credit-kind": bucket.kind, className: "rounded-2xl border border-slate-200/70 bg-white/85 px-3 py-3 text-sm shadow-sm transition-transform hover:-translate-y-0.5 hover:shadow-md dark:border-slate-800/60 dark:bg-slate-900/60 sm:px-4", children: [jsxs("div", { className: "grid grid-cols-[1fr_auto] items-center gap-3 text-xs sm:text-sm", children: [jsx("span", { className: "flex min-w-0 items-center gap-2", children: jsx("span", { className: "max-w-full truncate rounded-full bg-purple-50 px-2 py-1 text-xs text-purple-600 shadow-sm dark:bg-purple-500/20 dark:text-purple-100 sm:text-sm", children: bucket.computedLabel }) }), jsx("span", { className: "flex min-w-0 justify-end", children: jsx("span", { className: "text-right text-base font-semibold leading-tight text-gray-500 dark:text-slate-100 sm:text-lg", title: balanceDisplay, children: balanceDisplay }) })] }), jsx("div", { className: "mt-3 flex justify-end gap-2", children: jsxs("span", { className: "text-[11px] text-gray-500 dark:text-slate-100 sm:text-xs", children: [translations.expiredAtLabel, ": ", bucket.expiresAt] }) })] }, bucket.kind));
235
235
  }) })) : (jsx("button", { type: "button", onClick: expandBuckets, className: "w-full rounded-2xl border border-slate-200/70 bg-white/85 p-6 sm:px-4 text-sm shadow-sm transition-transform hover:-translate-y-0.5 hover:shadow-md dark:border-slate-800/60 dark:bg-slate-900/60 hover:text-purple-500", children: translations.expandDetail }))) : (jsx("div", { className: "w-full rounded-2xl border border-slate-200/70 bg-white/85 p-6 sm:px-4 text-sm shadow-sm transition-transform dark:border-slate-800/60 dark:bg-slate-900/60 text-center", children: translations.bucketsEmpty })), jsx(GradientButton, { title: translations.onetimeBuy, icon: jsx(globalLucideIcons.ShoppingCart, {}), align: "center", className: "w-full text-sm sm:text-base", onClick: handleOnetimeAction })] })] }));
236
236
  }
237
237
  function deriveStatus(expiresAt, thresholdDays = 7) {
@@ -23,7 +23,7 @@ function FooterEmail({ email, clickToCopyText, copiedText, children }) {
23
23
  // silent fail
24
24
  }
25
25
  });
26
- return (jsxRuntime.jsxs("div", { className: "relative group", children: [jsxRuntime.jsx("div", { className: "absolute left-2/3 -translate-x-1/4 bottom-full pb-1 hidden group-hover:block z-10", children: jsxRuntime.jsx("div", { className: "bg-zinc-600 text-white text-xs rounded px-3 py-1 whitespace-nowrap shadow-lg cursor-pointer select-text", onMouseDown: handleCopy, title: displayTitle, children: copied ? displayCopied : email }) }), jsxRuntime.jsx("a", { href: `mailto:${email}`, className: "flex items-center space-x-1 underline cursor-pointer px-2", children: children })] }));
26
+ return (jsxRuntime.jsxs("div", { className: "relative group", children: [jsxRuntime.jsx("div", { className: "absolute right-0 sm:right-auto sm:left-2/3 sm:-translate-x-1/4 bottom-full pb-1 hidden group-hover:block z-10", children: jsxRuntime.jsx("div", { className: "bg-zinc-600 text-white text-xs rounded px-3 py-1 whitespace-nowrap shadow-lg cursor-pointer select-text", onMouseDown: handleCopy, title: displayTitle, children: copied ? displayCopied : email }) }), jsxRuntime.jsx("a", { href: `mailto:${email}`, className: "flex items-center space-x-1 underline cursor-pointer px-2", children: children })] }));
27
27
  }
28
28
 
29
29
  exports.FooterEmail = FooterEmail;
@@ -21,7 +21,7 @@ function FooterEmail({ email, clickToCopyText, copiedText, children }) {
21
21
  // silent fail
22
22
  }
23
23
  });
24
- return (jsxs("div", { className: "relative group", children: [jsx("div", { className: "absolute left-2/3 -translate-x-1/4 bottom-full pb-1 hidden group-hover:block z-10", children: jsx("div", { className: "bg-zinc-600 text-white text-xs rounded px-3 py-1 whitespace-nowrap shadow-lg cursor-pointer select-text", onMouseDown: handleCopy, title: displayTitle, children: copied ? displayCopied : email }) }), jsx("a", { href: `mailto:${email}`, className: "flex items-center space-x-1 underline cursor-pointer px-2", children: children })] }));
24
+ return (jsxs("div", { className: "relative group", children: [jsx("div", { className: "absolute right-0 sm:right-auto sm:left-2/3 sm:-translate-x-1/4 bottom-full pb-1 hidden group-hover:block z-10", children: jsx("div", { className: "bg-zinc-600 text-white text-xs rounded px-3 py-1 whitespace-nowrap shadow-lg cursor-pointer select-text", onMouseDown: handleCopy, title: displayTitle, children: copied ? displayCopied : email }) }), jsx("a", { href: `mailto:${email}`, className: "flex items-center space-x-1 underline cursor-pointer px-2", children: children })] }));
25
25
  }
26
26
 
27
27
  export { FooterEmail };
@@ -1,3 +1,7 @@
1
- export declare function Footer({ locale }: {
1
+ interface FooterProps {
2
2
  locale: string;
3
- }): Promise<import("react/jsx-runtime").JSX.Element>;
3
+ localPrefixAsNeeded?: boolean;
4
+ defaultLocale?: string;
5
+ }
6
+ export declare function Footer({ locale, localPrefixAsNeeded, defaultLocale }: FooterProps): Promise<import("react/jsx-runtime").JSX.Element>;
7
+ export {};
@@ -7,9 +7,10 @@ var server$1 = require('@windrun-huaiin/base-ui/components/server');
7
7
  var Link = require('next/link');
8
8
  var footerEmail = require('./footer-email.js');
9
9
  var tIntl = require('../lib/t-intl.js');
10
+ var lib = require('@windrun-huaiin/lib');
10
11
 
11
12
  function Footer(_a) {
12
- return tslib_es6.__awaiter(this, arguments, void 0, function* ({ locale }) {
13
+ return tslib_es6.__awaiter(this, arguments, void 0, function* ({ locale, localPrefixAsNeeded = true, defaultLocale = 'en' }) {
13
14
  const tFooter = yield server.getTranslations({ locale, namespace: 'footer' });
14
15
  const company = tIntl.safeT(tFooter, 'company', '');
15
16
  const data = {
@@ -21,7 +22,7 @@ function Footer(_a) {
21
22
  clickToCopyText: tIntl.safeT(tFooter, 'clickToCopy', 'Click to copy'),
22
23
  copiedText: tIntl.safeT(tFooter, 'copied', 'Copied!'),
23
24
  };
24
- return (jsxRuntime.jsx("div", { className: "mb-10 w-full mx-auto border-t-purple-700/80 border-t", children: jsxRuntime.jsx("footer", { children: jsxRuntime.jsxs("div", { className: "w-full flex flex-col items-center justify-center px-4 py-8 space-y-3", children: [jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-center gap-x-2 gap-y-2 text-xs sm:text-sm sm:gap-x-6", children: [jsxRuntime.jsxs(Link, { href: `/${locale}/legal/terms`, className: "flex items-center space-x-1 hover:underline", children: [jsxRuntime.jsx(server$1.globalLucideIcons.ReceiptText, { className: "h-3.5 w-3.5" }), jsxRuntime.jsx("span", { children: data.terms })] }), jsxRuntime.jsxs(Link, { href: `/${locale}/legal/privacy`, className: "flex items-center space-x-1 hover:underline", children: [jsxRuntime.jsx(server$1.globalLucideIcons.ShieldUser, { className: "h-3.5 w-3.5" }), jsxRuntime.jsx("span", { children: data.privacy })] }), jsxRuntime.jsxs(footerEmail.FooterEmail, { email: data.email, clickToCopyText: data.clickToCopyText, copiedText: data.copiedText, children: [jsxRuntime.jsx(server$1.globalLucideIcons.Mail, { className: "h-3.5 w-3.5" }), jsxRuntime.jsx("span", { children: data.contactUs })] })] }), jsxRuntime.jsx("div", { className: "text-xs sm:text-sm text-center", children: jsxRuntime.jsx("span", { children: data.copyright }) })] }) }) }));
25
+ return (jsxRuntime.jsx("div", { className: "mb-10 w-full mx-auto border-t-purple-700/80 border-t", children: jsxRuntime.jsx("footer", { children: jsxRuntime.jsxs("div", { className: "w-full flex flex-col items-center justify-center px-4 py-8 space-y-3", children: [jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-center gap-x-2 gap-y-2 text-xs sm:text-sm sm:gap-x-6", children: [jsxRuntime.jsxs(Link, { href: lib.getAsNeededLocalizedUrl(locale, "/legal/terms", localPrefixAsNeeded, defaultLocale), className: "flex items-center space-x-1 hover:underline", children: [jsxRuntime.jsx(server$1.globalLucideIcons.ReceiptText, { className: "h-3.5 w-3.5" }), jsxRuntime.jsx("span", { children: data.terms })] }), jsxRuntime.jsxs(Link, { href: lib.getAsNeededLocalizedUrl(locale, "/legal/privacy", localPrefixAsNeeded, defaultLocale), className: "flex items-center space-x-1 hover:underline", children: [jsxRuntime.jsx(server$1.globalLucideIcons.ShieldUser, { className: "h-3.5 w-3.5" }), jsxRuntime.jsx("span", { children: data.privacy })] }), jsxRuntime.jsxs(footerEmail.FooterEmail, { email: data.email, clickToCopyText: data.clickToCopyText, copiedText: data.copiedText, children: [jsxRuntime.jsx(server$1.globalLucideIcons.Mail, { className: "h-3.5 w-3.5" }), jsxRuntime.jsx("span", { children: data.contactUs })] })] }), jsxRuntime.jsx("div", { className: "text-xs sm:text-sm text-center", children: jsxRuntime.jsx("span", { children: data.copyright }) })] }) }) }));
25
26
  });
26
27
  }
27
28
 
@@ -5,9 +5,10 @@ import { globalLucideIcons } from '@windrun-huaiin/base-ui/components/server';
5
5
  import Link from 'next/link';
6
6
  import { FooterEmail } from './footer-email.mjs';
7
7
  import { safeT } from '../lib/t-intl.mjs';
8
+ import { getAsNeededLocalizedUrl } from '@windrun-huaiin/lib';
8
9
 
9
10
  function Footer(_a) {
10
- return __awaiter(this, arguments, void 0, function* ({ locale }) {
11
+ return __awaiter(this, arguments, void 0, function* ({ locale, localPrefixAsNeeded = true, defaultLocale = 'en' }) {
11
12
  const tFooter = yield getTranslations({ locale, namespace: 'footer' });
12
13
  const company = safeT(tFooter, 'company', '');
13
14
  const data = {
@@ -19,7 +20,7 @@ function Footer(_a) {
19
20
  clickToCopyText: safeT(tFooter, 'clickToCopy', 'Click to copy'),
20
21
  copiedText: safeT(tFooter, 'copied', 'Copied!'),
21
22
  };
22
- return (jsx("div", { className: "mb-10 w-full mx-auto border-t-purple-700/80 border-t", children: jsx("footer", { children: jsxs("div", { className: "w-full flex flex-col items-center justify-center px-4 py-8 space-y-3", children: [jsxs("div", { className: "flex flex-wrap items-center justify-center gap-x-2 gap-y-2 text-xs sm:text-sm sm:gap-x-6", children: [jsxs(Link, { href: `/${locale}/legal/terms`, className: "flex items-center space-x-1 hover:underline", children: [jsx(globalLucideIcons.ReceiptText, { className: "h-3.5 w-3.5" }), jsx("span", { children: data.terms })] }), jsxs(Link, { href: `/${locale}/legal/privacy`, className: "flex items-center space-x-1 hover:underline", children: [jsx(globalLucideIcons.ShieldUser, { className: "h-3.5 w-3.5" }), jsx("span", { children: data.privacy })] }), jsxs(FooterEmail, { email: data.email, clickToCopyText: data.clickToCopyText, copiedText: data.copiedText, children: [jsx(globalLucideIcons.Mail, { className: "h-3.5 w-3.5" }), jsx("span", { children: data.contactUs })] })] }), jsx("div", { className: "text-xs sm:text-sm text-center", children: jsx("span", { children: data.copyright }) })] }) }) }));
23
+ return (jsx("div", { className: "mb-10 w-full mx-auto border-t-purple-700/80 border-t", children: jsx("footer", { children: jsxs("div", { className: "w-full flex flex-col items-center justify-center px-4 py-8 space-y-3", children: [jsxs("div", { className: "flex flex-wrap items-center justify-center gap-x-2 gap-y-2 text-xs sm:text-sm sm:gap-x-6", children: [jsxs(Link, { href: getAsNeededLocalizedUrl(locale, "/legal/terms", localPrefixAsNeeded, defaultLocale), className: "flex items-center space-x-1 hover:underline", children: [jsx(globalLucideIcons.ReceiptText, { className: "h-3.5 w-3.5" }), jsx("span", { children: data.terms })] }), jsxs(Link, { href: getAsNeededLocalizedUrl(locale, "/legal/privacy", localPrefixAsNeeded, defaultLocale), className: "flex items-center space-x-1 hover:underline", children: [jsx(globalLucideIcons.ShieldUser, { className: "h-3.5 w-3.5" }), jsx("span", { children: data.privacy })] }), jsxs(FooterEmail, { email: data.email, clickToCopyText: data.clickToCopyText, copiedText: data.copiedText, children: [jsx(globalLucideIcons.Mail, { className: "h-3.5 w-3.5" }), jsx("span", { children: data.contactUs })] })] }), jsx("div", { className: "text-xs sm:text-sm text-center", children: jsx("span", { children: data.copyright }) })] }) }) }));
23
24
  });
24
25
  }
25
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windrun-huaiin/third-ui",
3
- "version": "11.1.0",
3
+ "version": "12.0.0",
4
4
  "description": "Third-party integrated UI components for windrun-huaiin projects",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -16,6 +16,11 @@
16
16
  "import": "./dist/clerk/server.mjs",
17
17
  "require": "./dist/clerk/server.js"
18
18
  },
19
+ "./clerk/patch/optional-auth": {
20
+ "types": "./dist/clerk/patch/optional-auth.d.ts",
21
+ "import": "./dist/clerk/patch/optional-auth.mjs",
22
+ "require": "./dist/clerk/patch/optional-auth.js"
23
+ },
19
24
  "./fingerprint": {
20
25
  "types": "./dist/clerk/fingerprint/index.d.ts",
21
26
  "import": "./dist/clerk/fingerprint/index.mjs",
@@ -81,8 +86,8 @@
81
86
  "react-medium-image-zoom": "^5.2.14",
82
87
  "swiper": "^12.0.3",
83
88
  "zod": "^4.1.12",
84
- "@windrun-huaiin/base-ui": "^11.0.1",
85
- "@windrun-huaiin/lib": "^11.0.1"
89
+ "@windrun-huaiin/base-ui": "^12.0.0",
90
+ "@windrun-huaiin/lib": "^12.0.0"
86
91
  },
87
92
  "peerDependencies": {
88
93
  "clsx": "^2.1.1",
@@ -7,6 +7,10 @@ import React from 'react';
7
7
  interface ClerkProviderClientProps {
8
8
  children: React.ReactNode;
9
9
  locale: string;
10
+ // Whether localePrefix is set to 'as-needed' (default: true)
11
+ localPrefixAsNeeded?: boolean;
12
+ // The default locale used by the host app (default: 'en')
13
+ defaultLocale?: string;
10
14
  signInUrl?: string;
11
15
  signUpUrl?: string;
12
16
  fallbackSignInUrl?: string;
@@ -17,13 +21,25 @@ interface ClerkProviderClientProps {
17
21
  export function ClerkProviderClient({
18
22
  children,
19
23
  locale,
24
+ localPrefixAsNeeded = true,
25
+ defaultLocale = 'en',
20
26
  signInUrl,
21
27
  signUpUrl,
22
28
  fallbackSignInUrl,
23
29
  fallbackSignUpUrl,
24
30
  waitlistUrl,
25
31
  }: ClerkProviderClientProps) {
26
- const currentLocalization = clerkIntl[locale as keyof typeof clerkIntl];
32
+ const currentLocalization =
33
+ clerkIntl[locale as keyof typeof clerkIntl] ??
34
+ clerkIntl[defaultLocale as keyof typeof clerkIntl] ??
35
+ clerkIntl.en;
36
+
37
+ // In as-needed mode, skip prefixing for the default locale so /sign-in stays unprefixed.
38
+ const shouldPrefixLocale = localPrefixAsNeeded ? locale !== defaultLocale : true;
39
+ const localeSegment = shouldPrefixLocale && locale ? `/${locale}` : '';
40
+
41
+ const buildUrl = (path?: string) =>
42
+ path ? `${localeSegment}${path}` : undefined;
27
43
 
28
44
  // build the ClerkProvider props, only add when the parameter is not empty
29
45
  const clerkProviderProps: Record<string, any> = {
@@ -31,20 +47,29 @@ export function ClerkProviderClient({
31
47
  };
32
48
 
33
49
  // Only add URL when the parameter is not empty
34
- if (signInUrl) {
35
- clerkProviderProps.signInUrl = `/${locale}${signInUrl}`;
50
+ const signInWithLocale = buildUrl(signInUrl);
51
+ if (signInWithLocale) {
52
+ clerkProviderProps.signInUrl = signInWithLocale;
36
53
  }
37
- if (signUpUrl) {
38
- clerkProviderProps.signUpUrl = `/${locale}${signUpUrl}`;
54
+
55
+ const signUpWithLocale = buildUrl(signUpUrl);
56
+ if (signUpWithLocale) {
57
+ clerkProviderProps.signUpUrl = signUpWithLocale;
39
58
  }
40
- if (fallbackSignInUrl) {
41
- clerkProviderProps.signInFallbackRedirectUrl = `/${locale}${fallbackSignInUrl}`;
59
+
60
+ const signInFallbackWithLocale = buildUrl(fallbackSignInUrl);
61
+ if (signInFallbackWithLocale) {
62
+ clerkProviderProps.signInFallbackRedirectUrl = signInFallbackWithLocale;
42
63
  }
43
- if (fallbackSignUpUrl) {
44
- clerkProviderProps.signUpFallbackRedirectUrl = `/${locale}${fallbackSignUpUrl}`;
64
+
65
+ const signUpFallbackWithLocale = buildUrl(fallbackSignUpUrl);
66
+ if (signUpFallbackWithLocale) {
67
+ clerkProviderProps.signUpFallbackRedirectUrl = signUpFallbackWithLocale;
45
68
  }
46
- if (waitlistUrl) {
47
- clerkProviderProps.waitlistUrl = `/${locale}${waitlistUrl}`;
69
+
70
+ const waitlistWithLocale = buildUrl(waitlistUrl);
71
+ if (waitlistWithLocale) {
72
+ clerkProviderProps.waitlistUrl = waitlistWithLocale;
48
73
  }
49
74
 
50
75
  // console.log('ClerkProviderClient props:', clerkProviderProps);
@@ -54,4 +79,4 @@ export function ClerkProviderClient({
54
79
  {children}
55
80
  </ClerkProvider>
56
81
  );
57
- }
82
+ }
@@ -0,0 +1,24 @@
1
+ import { auth } from '@clerk/nextjs/server';
2
+
3
+ export type OptionalAuthResult = {
4
+ userId: string | null;
5
+ sessionId: string | null;
6
+ raw: Awaited<ReturnType<typeof auth>> | null;
7
+ };
8
+
9
+ /**
10
+ * 可选鉴权:在缺少 Clerk 标记或未登录时返回 null,避免 auth() 抛错。
11
+ * 仅供服务端使用,请从 @third-ui/clerk/patch/optional-auth 导入。
12
+ */
13
+ export async function getOptionalAuth(): Promise<OptionalAuthResult> {
14
+ try {
15
+ const res = await auth();
16
+ return {
17
+ userId: res.userId ?? null,
18
+ sessionId: res.sessionId ?? null,
19
+ raw: res,
20
+ };
21
+ } catch {
22
+ return { userId: null, sessionId: null, raw: null };
23
+ }
24
+ }
@@ -86,6 +86,14 @@ export interface CustomHomeLayoutProps {
86
86
  * Customize the order of header action items.
87
87
  */
88
88
  actionOrders?: HeaderActionOrders;
89
+ /**
90
+ * Whether localePrefix is set to 'as-needed' (default: true)
91
+ */
92
+ localPrefixAsNeeded?: boolean;
93
+ /**
94
+ * The default locale for the application (default: 'en')
95
+ */
96
+ defaultLocale?: string;
89
97
  children?: ReactNode;
90
98
  }
91
99
 
@@ -112,6 +120,8 @@ export function CustomHomeLayout({
112
120
  style,
113
121
  floatingNav = false,
114
122
  actionOrders,
123
+ localPrefixAsNeeded = true,
124
+ defaultLocale = 'en',
115
125
  }: CustomHomeLayoutProps) {
116
126
  const resolvedBannerHeight = bannerHeight ?? (showBanner ? 3 : 0.5);
117
127
  const resolvedPaddingTop =
@@ -161,7 +171,7 @@ export function CustomHomeLayout({
161
171
  style={layoutStyle}
162
172
  >
163
173
  {children}
164
- {showFooter ? footer ?? <Footer locale={locale} /> : null}
174
+ {showFooter ? footer ?? <Footer locale={locale} localPrefixAsNeeded={localPrefixAsNeeded} defaultLocale={defaultLocale} /> : null}
165
175
  {showGoToTop ? goToTop ?? <GoToTop /> : null}
166
176
  </HomeLayout>
167
177
  </>
@@ -2,6 +2,7 @@ import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/page
2
2
  import { ReactNode, ReactElement, cloneElement } from 'react';
3
3
  import { TocFooterWrapper } from '@third-ui/fuma/mdx/toc-footer-wrapper';
4
4
  import type { LLMCopyButtonProps, LLMCopyButton } from '@third-ui/fuma/mdx/toc-base';
5
+ import { getAsNeededLocalizedUrl } from '@windrun-huaiin/lib';
5
6
 
6
7
  interface FumaPageParams {
7
8
  /*
@@ -36,7 +37,7 @@ interface FumaPageParams {
36
37
  * The fallback page component to use when the page is not found
37
38
  */
38
39
  FallbackPage: React.ComponentType<{ siteIcon: ReactNode }>;
39
- /*
40
+ /*
40
41
  * Supported locales for generating alternates metadata, defaults to ['en']
41
42
  */
42
43
  supportedLocales?: string[];
@@ -49,6 +50,16 @@ interface FumaPageParams {
49
50
 
50
51
  // default false, for mobile style can cause issue
51
52
  showTableOfContentPopover?: boolean;
53
+
54
+ /*
55
+ * Whether localePrefix is set to 'as-needed' (default: true)
56
+ */
57
+ localPrefixAsNeeded?: boolean;
58
+
59
+ /*
60
+ * The default locale for the application (default: 'en')
61
+ */
62
+ defaultLocale?: string;
52
63
  }
53
64
 
54
65
  export function createFumaPage({
@@ -64,11 +75,14 @@ export function createFumaPage({
64
75
  showBreadcrumb = true,
65
76
  showTableOfContent = true,
66
77
  showTableOfContentPopover = false,
78
+ localPrefixAsNeeded = true,
79
+ defaultLocale = 'en',
67
80
  }: FumaPageParams) {
68
81
  const Page = async function Page({ params }: { params: Promise<{ locale: string; slug?: string[] }> }) {
69
82
  const { slug, locale } = await params;
70
83
  const page = mdxContentSource.getPage(slug, locale);
71
84
  if (!page) {
85
+ console.log('[FumaPage] missing page', { slug, locale, available: mdxContentSource.pageTree?.[locale]?.children?.map((c: any) => c.url) });
72
86
  return <FallbackPage siteIcon={siteIcon} />;
73
87
  }
74
88
 
@@ -129,14 +143,15 @@ export function createFumaPage({
129
143
  const baseRoute = mdxSourceDir.replace('src/mdx/', '');
130
144
  // build the current page path
131
145
  const currentPath = slug ? slug.join('/') : '';
132
- const currentUrl = `${baseUrl}/${locale}/${baseRoute}${currentPath ? `/${currentPath}` : ''}`;
146
+ const localizedPath = getAsNeededLocalizedUrl(locale || defaultLocale, `/${baseRoute}${currentPath ? `/${currentPath}` : ''}`, localPrefixAsNeeded, defaultLocale);
147
+ const currentUrl = `${baseUrl}${localizedPath}`;
133
148
 
134
149
  // generate the seo language map
135
150
  const seoLanguageMap: Record<string, string> = {};
136
151
 
137
-
138
152
  supportedLocales.forEach(loc => {
139
- seoLanguageMap[loc] = `${baseUrl}/${loc}/${baseRoute}${currentPath ? `/${currentPath}` : ''}`;
153
+ const seoPath = getAsNeededLocalizedUrl(loc, `/${baseRoute}${currentPath ? `/${currentPath}` : ''}`, localPrefixAsNeeded, defaultLocale);
154
+ seoLanguageMap[loc] = `${baseUrl}${seoPath}`;
140
155
  });
141
156
 
142
157
  return {
@@ -1,6 +1,7 @@
1
1
  import type { MetadataRoute } from 'next';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
+ import { getAsNeededLocalizedUrl } from '@windrun-huaiin/lib';
4
5
 
5
6
  /**
6
7
  * Generate robots.txt content
@@ -23,13 +24,17 @@ export function generateRobots(baseUrl: string): MetadataRoute.Robots {
23
24
  * @param locales - Supported locales array
24
25
  * @param mdxSourceDir - MDX source directory path
25
26
  * @param openMdxSEOSiteMap - Whether to include MDX content in sitemap, default is true
27
+ * @param localPrefixAsNeeded - Whether localePrefix is set to 'as-needed' (default: true)
28
+ * @param defaultLocale - The default locale for the application (default: 'en')
26
29
  * @returns Sitemap entries
27
30
  */
28
31
  export function generateSitemap(
29
32
  baseUrl: string,
30
33
  locales: string[],
31
34
  mdxSourceDir: string,
32
- openMdxSEOSiteMap: boolean = true
35
+ openMdxSEOSiteMap: boolean = true,
36
+ localPrefixAsNeeded: boolean = true,
37
+ defaultLocale: string = 'en'
33
38
  ): MetadataRoute.Sitemap {
34
39
  // 2. handle index.mdx (blog start page) and other slugs
35
40
  const blogRoutes: MetadataRoute.Sitemap = [];
@@ -46,16 +51,18 @@ export function generateSitemap(
46
51
  for (const locale of locales) {
47
52
  for (const f of blogFiles) {
48
53
  if (f === 'index.mdx') {
54
+ const localizedPath = getAsNeededLocalizedUrl(locale, '/blog', localPrefixAsNeeded, defaultLocale);
49
55
  blogRoutes.push({
50
- url: `${baseUrl}/${locale}/blog`,
56
+ url: `${baseUrl}${localizedPath}`,
51
57
  lastModified: new Date(),
52
58
  changeFrequency: 'daily',
53
59
  priority: 1.0
54
60
  });
55
61
  } else {
56
62
  const slug = f.replace(/\.mdx$/, '');
63
+ const localizedPath = getAsNeededLocalizedUrl(locale, `/blog/${slug}`, localPrefixAsNeeded, defaultLocale);
57
64
  blogRoutes.push({
58
- url: `${baseUrl}/${locale}/blog/${slug}`,
65
+ url: `${baseUrl}${localizedPath}`,
59
66
  lastModified: new Date(),
60
67
  changeFrequency: f === 'ioc.mdx' ? 'daily' : 'monthly',
61
68
  priority: 0.8
@@ -71,12 +78,15 @@ export function generateSitemap(
71
78
  }
72
79
 
73
80
  // 3. main page (all language versions)
74
- const mainRoutes = locales.map(locale => ({
75
- url: `${baseUrl}/${locale}`,
76
- lastModified: new Date(),
77
- changeFrequency: 'weekly' as const,
78
- priority: 1.0
79
- }));
81
+ const mainRoutes = locales.map(locale => {
82
+ const localizedPath = getAsNeededLocalizedUrl(locale, '/', localPrefixAsNeeded, defaultLocale);
83
+ return {
84
+ url: `${baseUrl}${localizedPath}`,
85
+ lastModified: new Date(),
86
+ changeFrequency: 'weekly' as const,
87
+ priority: 1.0
88
+ };
89
+ });
80
90
 
81
91
  return openMdxSEOSiteMap ? [...mainRoutes, ...blogRoutes] : [...mainRoutes];
82
92
  }
@@ -98,21 +108,25 @@ export function createRobotsHandler(baseUrl: string) {
98
108
  * @param locales - Supported locales array
99
109
  * @param mdxSourceDir - MDX source directory path, default is empty
100
110
  * @param openMdxSEOSiteMap - Whether to include MDX content in sitemap, default is true
111
+ * @param localPrefixAsNeeded - Whether localePrefix is set to 'as-needed' (default: true)
112
+ * @param defaultLocale - The default locale for the application (default: 'en')
101
113
  * @returns Sitemap handler function
102
114
  */
103
115
  export function createSitemapHandler(
104
116
  baseUrl: string,
105
117
  locales: string[],
106
118
  mdxSourceDir: string = '',
107
- openMdxSEOSiteMap: boolean = true
119
+ openMdxSEOSiteMap: boolean = true,
120
+ localPrefixAsNeeded: boolean = true,
121
+ defaultLocale: string = 'en'
108
122
  ) {
109
123
  // force static generation
110
124
  const sitemapHandler = function sitemap(): MetadataRoute.Sitemap {
111
- return generateSitemap(baseUrl, locales, mdxSourceDir, openMdxSEOSiteMap);
125
+ return generateSitemap(baseUrl, locales, mdxSourceDir, openMdxSEOSiteMap, localPrefixAsNeeded, defaultLocale);
112
126
  };
113
-
127
+
114
128
  // Add static generation directive
115
129
  (sitemapHandler as any).dynamic = 'force-static';
116
-
130
+
117
131
  return sitemapHandler;
118
132
  }
@@ -370,7 +370,7 @@ export function CreditOverviewClient({
370
370
  <div className="flex flex-col gap-2 sm:gap-3">
371
371
  <div className="flex items-center justify-start rounded-full ">
372
372
  <icons.Gem aria-hidden className="mr-2 h-6 w-6 sm:h-8 sm:w-8" />
373
- <span className="text-sm font-medium sm:text-base">{translations.totalLabel}</span>
373
+ <span className="text-base sm:text-lg">{translations.totalLabel}</span>
374
374
  </div>
375
375
  <div className="flex justify-center text-3xl font-semibold leading-tight sm:text-4xl">
376
376
  {formatNumber(locale, data.totalBalance)}
@@ -405,7 +405,7 @@ export function CreditOverviewClient({
405
405
  {/* Credit Details Section */}
406
406
  <section className="relative flex flex-col gap-3 rounded-2xl border p-4 shadow-inner sm:gap-2 sm:p-5">
407
407
  <div className="flex flex-wrap items-center justify-between gap-2">
408
- <h3 className="text-base font-semibold text-gray-500 dark:text-slate-100 sm:text-lg">
408
+ <h3 className="text-base text-gray-500 dark:text-slate-100 sm:text-lg">
409
409
  {translations.bucketsTitle}
410
410
  </h3>
411
411
  {hasBuckets ? (
@@ -437,7 +437,7 @@ export function CreditOverviewClient({
437
437
  >
438
438
  <div className="grid grid-cols-[1fr_auto] items-center gap-3 text-xs sm:text-sm">
439
439
  <span className="flex min-w-0 items-center gap-2">
440
- <span className="max-w-full truncate rounded-full bg-purple-50 px-2 py-1 text-xs font-semibold text-purple-600 shadow-sm dark:bg-purple-500/20 dark:text-purple-100 sm:text-sm">
440
+ <span className="max-w-full truncate rounded-full bg-purple-50 px-2 py-1 text-xs text-purple-600 shadow-sm dark:bg-purple-500/20 dark:text-purple-100 sm:text-sm">
441
441
  {bucket.computedLabel}
442
442
  </span>
443
443
  </span>
@@ -451,7 +451,7 @@ export function CreditOverviewClient({
451
451
  </span>
452
452
  </div>
453
453
  <div className="mt-3 flex justify-end gap-2">
454
- <span className="text-[11px] font-semibold text-gray-500 dark:text-slate-100 sm:text-xs">
454
+ <span className="text-[11px] text-gray-500 dark:text-slate-100 sm:text-xs">
455
455
  {translations.expiredAtLabel}: {bucket.expiresAt}
456
456
  </span>
457
457
  </div>
@@ -32,7 +32,7 @@ export function FooterEmail({ email, clickToCopyText, copiedText, children }: Fo
32
32
 
33
33
  return (
34
34
  <div className="relative group">
35
- <div className="absolute left-2/3 -translate-x-1/4 bottom-full pb-1 hidden group-hover:block z-10">
35
+ <div className="absolute right-0 sm:right-auto sm:left-2/3 sm:-translate-x-1/4 bottom-full pb-1 hidden group-hover:block z-10">
36
36
  <div
37
37
  className="bg-zinc-600 text-white text-xs rounded px-3 py-1 whitespace-nowrap shadow-lg cursor-pointer select-text"
38
38
  onMouseDown={handleCopy}