@windrun-huaiin/third-ui 11.0.10 → 11.1.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.
@@ -20,7 +20,7 @@ function CustomHomeLayout({ locale, options, children, showBanner = false, banne
20
20
  return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [banner !== null && banner !== void 0 ? banner : (jsxRuntime.jsx(fumaBannerSuit.FumaBannerSuit, { locale: locale, showBanner: showBanner, floating: floatingNav })), jsxRuntime.jsxs(home.HomeLayout, Object.assign({}, homeLayoutProps, { nav: Object.assign(Object.assign({}, navOptions), { component: header }), className: 'bg-neutral-100 dark:bg-neutral-900', style: layoutStyle, children: [children, showFooter ? footer$1 !== null && footer$1 !== void 0 ? footer$1 : jsxRuntime.jsx(footer.Footer, { locale: locale }) : null, showGoToTop ? goToTop$1 !== null && goToTop$1 !== void 0 ? goToTop$1 : jsxRuntime.jsx(goToTop.GoToTop, {}) : null] }))] }));
21
21
  }
22
22
  function HomeTitle({ children, className, }) {
23
- return (jsxRuntime.jsx("span", { className: `font-medium in-[.uwu]:hidden in-[header]:text-[clamp(8px,3vw,15px)]! ${className !== null && className !== void 0 ? className : ''}`, children: children }));
23
+ return (jsxRuntime.jsx("span", { className: `font-medium in-[.uwu]:hidden in-[header]:text-[clamp(12px,3vw,15px)]! ${className !== null && className !== void 0 ? className : ''}`, children: children }));
24
24
  }
25
25
 
26
26
  exports.CustomHomeLayout = CustomHomeLayout;
@@ -18,7 +18,7 @@ function CustomHomeLayout({ locale, options, children, showBanner = false, banne
18
18
  return (jsxs(Fragment, { children: [banner !== null && banner !== void 0 ? banner : (jsx(FumaBannerSuit, { locale: locale, showBanner: showBanner, floating: floatingNav })), jsxs(HomeLayout, Object.assign({}, homeLayoutProps, { nav: Object.assign(Object.assign({}, navOptions), { component: header }), className: 'bg-neutral-100 dark:bg-neutral-900', style: layoutStyle, children: [children, showFooter ? footer !== null && footer !== void 0 ? footer : jsx(Footer, { locale: locale }) : null, showGoToTop ? goToTop !== null && goToTop !== void 0 ? goToTop : jsx(GoToTop, {}) : null] }))] }));
19
19
  }
20
20
  function HomeTitle({ children, className, }) {
21
- return (jsx("span", { className: `font-medium in-[.uwu]:hidden in-[header]:text-[clamp(8px,3vw,15px)]! ${className !== null && className !== void 0 ? className : ''}`, children: children }));
21
+ return (jsx("span", { className: `font-medium in-[.uwu]:hidden in-[header]:text-[clamp(12px,3vw,15px)]! ${className !== null && className !== void 0 ? className : ''}`, children: children }));
22
22
  }
23
23
 
24
24
  export { CustomHomeLayout, HomeTitle };
@@ -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 };
@@ -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 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 })] }));
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 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 })] }));
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.10",
3
+ "version": "11.1.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",
@@ -177,7 +177,7 @@ export function HomeTitle({
177
177
  }) {
178
178
  return (
179
179
  <span
180
- className={`font-medium in-[.uwu]:hidden in-[header]:text-[clamp(8px,3vw,15px)]! ${className ?? ''}`}
180
+ className={`font-medium in-[.uwu]:hidden in-[header]:text-[clamp(12px,3vw,15px)]! ${className ?? ''}`}
181
181
  >
182
182
  {children}
183
183
  </span>
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
+ }
@@ -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 left-2/3 -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>