@windrun-huaiin/third-ui 11.0.11 → 11.1.1

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.
@@ -1,3 +1,4 @@
1
1
  export * from './clerk-intl';
2
2
  export * from './fuma-schema-check-util';
3
3
  export * from './seo-util';
4
+ export * from './t-intl';
@@ -3,6 +3,7 @@
3
3
  var clerkIntl = require('./clerk-intl.js');
4
4
  var fumaSchemaCheckUtil = require('./fuma-schema-check-util.js');
5
5
  var seoUtil = require('./seo-util.js');
6
+ var tIntl = require('./t-intl.js');
6
7
 
7
8
 
8
9
 
@@ -15,3 +16,4 @@ exports.createRobotsHandler = seoUtil.createRobotsHandler;
15
16
  exports.createSitemapHandler = seoUtil.createSitemapHandler;
16
17
  exports.generateRobots = seoUtil.generateRobots;
17
18
  exports.generateSitemap = seoUtil.generateSitemap;
19
+ exports.safeT = tIntl.safeT;
@@ -1,3 +1,4 @@
1
1
  export { clerkIntl } from './clerk-intl.mjs';
2
2
  export { createCommonDocsSchema, createCommonMetaSchema, createDateSchema, remarkInstallOptions } from './fuma-schema-check-util.mjs';
3
3
  export { createRobotsHandler, createSitemapHandler, generateRobots, generateSitemap } from './seo-util.mjs';
4
+ export { safeT } from './t-intl.mjs';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Safely retrieves a translation using the provided translator function.
3
+ * If the key does not exist (checked via `t.has(key)`), it returns the fallback value.
4
+ *
5
+ * @param t - The translator function from `next-intl` (e.g. obtained via `getTranslations` or `useTranslations`).
6
+ * @param key - The translation key to look up.
7
+ * @param fallback - The value to return if the key is missing. Defaults to an empty string.
8
+ * @param values - Optional values for interpolation in the translation string.
9
+ */
10
+ export declare function safeT(t: any, key: string, fallback?: string, values?: any): string;
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4
+ /**
5
+ * Safely retrieves a translation using the provided translator function.
6
+ * If the key does not exist (checked via `t.has(key)`), it returns the fallback value.
7
+ *
8
+ * @param t - The translator function from `next-intl` (e.g. obtained via `getTranslations` or `useTranslations`).
9
+ * @param key - The translation key to look up.
10
+ * @param fallback - The value to return if the key is missing. Defaults to an empty string.
11
+ * @param values - Optional values for interpolation in the translation string.
12
+ */
13
+ function safeT(t, key, fallback = '', values) {
14
+ // Ensure t has the .has method before calling it, just in case
15
+ if (t && typeof t.has === 'function' && t.has(key)) {
16
+ return t(key, values);
17
+ }
18
+ return fallback;
19
+ }
20
+
21
+ exports.safeT = safeT;
@@ -0,0 +1,19 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
+ /**
3
+ * Safely retrieves a translation using the provided translator function.
4
+ * If the key does not exist (checked via `t.has(key)`), it returns the fallback value.
5
+ *
6
+ * @param t - The translator function from `next-intl` (e.g. obtained via `getTranslations` or `useTranslations`).
7
+ * @param key - The translation key to look up.
8
+ * @param fallback - The value to return if the key is missing. Defaults to an empty string.
9
+ * @param values - Optional values for interpolation in the translation string.
10
+ */
11
+ function safeT(t, key, fallback = '', values) {
12
+ // Ensure t has the .has method before calling it, just in case
13
+ if (t && typeof t.has === 'function' && t.has(key)) {
14
+ return t(key, values);
15
+ }
16
+ return fallback;
17
+ }
18
+
19
+ export { safeT };
@@ -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) {
@@ -0,0 +1,8 @@
1
+ interface FooterEmailProps {
2
+ email: string;
3
+ clickToCopyText?: string;
4
+ copiedText?: string;
5
+ children: React.ReactNode;
6
+ }
7
+ export declare function FooterEmail({ email, clickToCopyText, copiedText, children }: FooterEmailProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var tslib_es6 = require('../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var React = require('react');
7
+
8
+ function FooterEmail({ email, clickToCopyText, copiedText, children }) {
9
+ const [copied, setCopied] = React.useState(false);
10
+ const displayTitle = clickToCopyText || 'Click to copy';
11
+ const displayCopied = copiedText || 'Copied!';
12
+ const handleCopy = (e) => tslib_es6.__awaiter(this, void 0, void 0, function* () {
13
+ // Prevent default to allow long press on mobile
14
+ e.preventDefault();
15
+ if (!navigator.clipboard)
16
+ return;
17
+ try {
18
+ yield navigator.clipboard.writeText(email);
19
+ setCopied(true);
20
+ setTimeout(() => setCopied(false), 2000);
21
+ }
22
+ catch (_a) {
23
+ // silent fail
24
+ }
25
+ });
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
+ }
28
+
29
+ exports.FooterEmail = FooterEmail;
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { __awaiter } from '../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.mjs';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import { useState } from 'react';
5
+
6
+ function FooterEmail({ email, clickToCopyText, copiedText, children }) {
7
+ const [copied, setCopied] = useState(false);
8
+ const displayTitle = clickToCopyText || 'Click to copy';
9
+ const displayCopied = copiedText || 'Copied!';
10
+ const handleCopy = (e) => __awaiter(this, void 0, void 0, function* () {
11
+ // Prevent default to allow long press on mobile
12
+ e.preventDefault();
13
+ if (!navigator.clipboard)
14
+ return;
15
+ try {
16
+ yield navigator.clipboard.writeText(email);
17
+ setCopied(true);
18
+ setTimeout(() => setCopied(false), 2000);
19
+ }
20
+ catch (_a) {
21
+ // silent fail
22
+ }
23
+ });
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
+ }
26
+
27
+ export { FooterEmail };
@@ -5,19 +5,23 @@ var jsxRuntime = require('react/jsx-runtime');
5
5
  var server = require('next-intl/server');
6
6
  var server$1 = require('@windrun-huaiin/base-ui/components/server');
7
7
  var Link = require('next/link');
8
+ var footerEmail = require('./footer-email.js');
9
+ var tIntl = require('../lib/t-intl.js');
8
10
 
9
11
  function Footer(_a) {
10
12
  return tslib_es6.__awaiter(this, arguments, void 0, function* ({ locale }) {
11
13
  const tFooter = yield server.getTranslations({ locale, namespace: 'footer' });
14
+ const company = tIntl.safeT(tFooter, 'company', '');
12
15
  const data = {
13
- terms: tFooter('terms', { defaultValue: 'Terms of Service' }),
14
- privacy: tFooter('privacy', { defaultValue: 'Privacy Policy' }),
15
- contactUs: tFooter('contactUs', { defaultValue: 'Contact Us' }),
16
- email: tFooter('email'),
17
- company: tFooter('company'),
18
- copyright: tFooter('copyright', { year: new Date().getFullYear(), name: tFooter('company') })
16
+ terms: tIntl.safeT(tFooter, 'terms', 'Terms of Service'),
17
+ privacy: tIntl.safeT(tFooter, 'privacy', 'Privacy Policy'),
18
+ contactUs: tIntl.safeT(tFooter, 'contactUs', 'Contact Us'),
19
+ email: tIntl.safeT(tFooter, 'email', ''),
20
+ copyright: tFooter('copyright', { year: new Date().getFullYear(), name: company }),
21
+ clickToCopyText: tIntl.safeT(tFooter, 'clickToCopy', 'Click to copy'),
22
+ copiedText: tIntl.safeT(tFooter, 'copied', 'Copied!'),
19
23
  };
20
- 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: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("div", { className: "relative group", children: [jsxRuntime.jsx("div", { className: "absolute left-2/3 -translate-x-1/4 bottom-full mb-1 hidden group-hover:block bg-zinc-600 text-white text-xs rounded px-3 py-1 whitespace-nowrap z-10 shadow-lg", children: data.email }), jsxRuntime.jsxs("a", { href: `mailto:${data.email}`, className: "flex items-center space-x-1 underline cursor-pointer px-2", 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 text-center", children: jsxRuntime.jsx("span", { children: data.copyright }) })] }) }) }));
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 }) })] }) }) }));
21
25
  });
22
26
  }
23
27
 
@@ -3,19 +3,23 @@ import { jsx, jsxs } from 'react/jsx-runtime';
3
3
  import { getTranslations } from 'next-intl/server';
4
4
  import { globalLucideIcons } from '@windrun-huaiin/base-ui/components/server';
5
5
  import Link from 'next/link';
6
+ import { FooterEmail } from './footer-email.mjs';
7
+ import { safeT } from '../lib/t-intl.mjs';
6
8
 
7
9
  function Footer(_a) {
8
10
  return __awaiter(this, arguments, void 0, function* ({ locale }) {
9
11
  const tFooter = yield getTranslations({ locale, namespace: 'footer' });
12
+ const company = safeT(tFooter, 'company', '');
10
13
  const data = {
11
- terms: tFooter('terms', { defaultValue: 'Terms of Service' }),
12
- privacy: tFooter('privacy', { defaultValue: 'Privacy Policy' }),
13
- contactUs: tFooter('contactUs', { defaultValue: 'Contact Us' }),
14
- email: tFooter('email'),
15
- company: tFooter('company'),
16
- copyright: tFooter('copyright', { year: new Date().getFullYear(), name: tFooter('company') })
14
+ terms: safeT(tFooter, 'terms', 'Terms of Service'),
15
+ privacy: safeT(tFooter, 'privacy', 'Privacy Policy'),
16
+ contactUs: safeT(tFooter, 'contactUs', 'Contact Us'),
17
+ email: safeT(tFooter, 'email', ''),
18
+ copyright: tFooter('copyright', { year: new Date().getFullYear(), name: company }),
19
+ clickToCopyText: safeT(tFooter, 'clickToCopy', 'Click to copy'),
20
+ copiedText: safeT(tFooter, 'copied', 'Copied!'),
17
21
  };
18
- 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: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("div", { className: "relative group", children: [jsx("div", { className: "absolute left-2/3 -translate-x-1/4 bottom-full mb-1 hidden group-hover:block bg-zinc-600 text-white text-xs rounded px-3 py-1 whitespace-nowrap z-10 shadow-lg", children: data.email }), jsxs("a", { href: `mailto:${data.email}`, className: "flex items-center space-x-1 underline cursor-pointer px-2", children: [jsx(globalLucideIcons.Mail, { className: "h-3.5 w-3.5" }), jsx("span", { children: data.contactUs })] })] })] }), jsx("div", { className: "text-xs text-center", children: jsx("span", { children: data.copyright }) })] }) }) }));
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 }) })] }) }) }));
19
23
  });
20
24
  }
21
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windrun-huaiin/third-ui",
3
- "version": "11.0.11",
3
+ "version": "11.1.1",
4
4
  "description": "Third-party integrated UI components for windrun-huaiin projects",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
package/src/lib/server.ts CHANGED
@@ -2,4 +2,5 @@
2
2
 
3
3
  export * from './clerk-intl';
4
4
  export * from './fuma-schema-check-util';
5
- export * from './seo-util';
5
+ export * from './seo-util';
6
+ export * from './t-intl';
@@ -0,0 +1,22 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
+ /**
3
+ * Safely retrieves a translation using the provided translator function.
4
+ * If the key does not exist (checked via `t.has(key)`), it returns the fallback value.
5
+ *
6
+ * @param t - The translator function from `next-intl` (e.g. obtained via `getTranslations` or `useTranslations`).
7
+ * @param key - The translation key to look up.
8
+ * @param fallback - The value to return if the key is missing. Defaults to an empty string.
9
+ * @param values - Optional values for interpolation in the translation string.
10
+ */
11
+ export function safeT(
12
+ t: any,
13
+ key: string,
14
+ fallback: string = '',
15
+ values?: any
16
+ ): string {
17
+ // Ensure t has the .has method before calling it, just in case
18
+ if (t && typeof t.has === 'function' && t.has(key)) {
19
+ return t(key, values);
20
+ }
21
+ return fallback;
22
+ }
@@ -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>
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ interface FooterEmailProps {
6
+ email: string;
7
+ clickToCopyText?: string;
8
+ copiedText?: string;
9
+ children: React.ReactNode;
10
+ }
11
+
12
+ export function FooterEmail({ email, clickToCopyText, copiedText, children }: FooterEmailProps) {
13
+ const [copied, setCopied] = useState(false);
14
+
15
+ const displayTitle = clickToCopyText || 'Click to copy';
16
+ const displayCopied = copiedText || 'Copied!';
17
+
18
+ const handleCopy = async (e: React.MouseEvent) => {
19
+ // Prevent default to allow long press on mobile
20
+ e.preventDefault();
21
+
22
+ if (!navigator.clipboard) return;
23
+
24
+ try {
25
+ await navigator.clipboard.writeText(email);
26
+ setCopied(true);
27
+ setTimeout(() => setCopied(false), 2000);
28
+ } catch {
29
+ // silent fail
30
+ }
31
+ };
32
+
33
+ return (
34
+ <div className="relative group">
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
+ <div
37
+ className="bg-zinc-600 text-white text-xs rounded px-3 py-1 whitespace-nowrap shadow-lg cursor-pointer select-text"
38
+ onMouseDown={handleCopy}
39
+ title={displayTitle}
40
+ >
41
+ {copied ? displayCopied : email}
42
+ </div>
43
+ </div>
44
+ <a
45
+ href={`mailto:${email}`}
46
+ className="flex items-center space-x-1 underline cursor-pointer px-2"
47
+ >
48
+ {children}
49
+ </a>
50
+ </div>
51
+ );
52
+ }
@@ -1,6 +1,8 @@
1
1
  import { getTranslations } from 'next-intl/server';
2
2
  import { globalLucideIcons as icons } from '@windrun-huaiin/base-ui/components/server';
3
3
  import Link from "next/link";
4
+ import { FooterEmail } from './footer-email';
5
+ import { safeT } from '../lib/t-intl';
4
6
 
5
7
  interface FooterData {
6
8
  terms: string;
@@ -9,25 +11,31 @@ interface FooterData {
9
11
  email: string;
10
12
  copyright: string;
11
13
  company: string;
14
+ clickToCopyText: string;
15
+ copiedText: string;
12
16
  }
13
17
 
14
18
  export async function Footer({ locale }: { locale: string }) {
15
19
  const tFooter = await getTranslations({ locale, namespace: 'footer' });
16
20
 
21
+ const company = safeT(tFooter, 'company', '');
22
+
17
23
  const data: FooterData = {
18
- terms: tFooter('terms', { defaultValue: 'Terms of Service' }),
19
- privacy: tFooter('privacy', { defaultValue: 'Privacy Policy' }),
20
- contactUs: tFooter('contactUs', { defaultValue: 'Contact Us' }),
21
- email: tFooter('email'),
22
- company: tFooter('company'),
23
- copyright: tFooter('copyright', { year: new Date().getFullYear(), name: tFooter('company') })
24
+ terms: safeT(tFooter, 'terms', 'Terms of Service'),
25
+ privacy: safeT(tFooter, 'privacy', 'Privacy Policy'),
26
+ contactUs: safeT(tFooter, 'contactUs', 'Contact Us'),
27
+ email: safeT(tFooter, 'email', ''),
28
+ company: company,
29
+ copyright: tFooter('copyright', { year: new Date().getFullYear(), name: company }),
30
+ clickToCopyText: safeT(tFooter, 'clickToCopy', 'Click to copy'),
31
+ copiedText: safeT(tFooter, 'copied', 'Copied!'),
24
32
  };
25
33
 
26
34
  return (
27
35
  <div className="mb-10 w-full mx-auto border-t-purple-700/80 border-t">
28
36
  <footer>
29
37
  <div className="w-full flex flex-col items-center justify-center px-4 py-8 space-y-3">
30
- <div className="flex flex-wrap items-center justify-center gap-x-2 gap-y-2 text-xs sm:gap-x-6">
38
+ <div className="flex flex-wrap items-center justify-center gap-x-2 gap-y-2 text-xs sm:text-sm sm:gap-x-6">
31
39
  <Link href={`/${locale}/legal/terms`} className="flex items-center space-x-1 hover:underline">
32
40
  <icons.ReceiptText className="h-3.5 w-3.5"/>
33
41
  <span>{data.terms}</span>
@@ -36,20 +44,12 @@ export async function Footer({ locale }: { locale: string }) {
36
44
  <icons.ShieldUser className="h-3.5 w-3.5"/>
37
45
  <span>{data.privacy}</span>
38
46
  </Link>
39
- <div className="relative group">
40
- <div className="absolute left-2/3 -translate-x-1/4 bottom-full mb-1 hidden group-hover:block bg-zinc-600 text-white text-xs rounded px-3 py-1 whitespace-nowrap z-10 shadow-lg">
41
- {data.email}
42
- </div>
43
- <a
44
- href={`mailto:${data.email}`}
45
- className="flex items-center space-x-1 underline cursor-pointer px-2"
46
- >
47
- <icons.Mail className="h-3.5 w-3.5"/>
48
- <span>{data.contactUs}</span>
49
- </a>
50
- </div>
47
+ <FooterEmail email={data.email} clickToCopyText={data.clickToCopyText} copiedText={data.copiedText}>
48
+ <icons.Mail className="h-3.5 w-3.5"/>
49
+ <span>{data.contactUs}</span>
50
+ </FooterEmail>
51
51
  </div>
52
- <div className="text-xs text-center">
52
+ <div className="text-xs sm:text-sm text-center">
53
53
  <span>{data.copyright}</span>
54
54
  </div>
55
55
  </div>