@windrun-huaiin/diaomao 30.0.0 → 31.0.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.
package/.env.local.txt CHANGED
@@ -30,7 +30,7 @@ NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/
30
30
  NEXT_PUBLIC_CLERK_WAITLIST_URL=/waitlist
31
31
 
32
32
  ## Media loading timeout
33
- NEXT_PUBLIC_DELAYED_IMG_SECONDS=2
33
+ NEXT_PUBLIC_DELAYED_IMG_SECONDS=1
34
34
  NEXT_PUBLIC_SUNO_EMBED_TIMEOUT_SECONDS=20
35
35
 
36
36
  ## OpenRouter·AI base config
package/messages/en.json CHANGED
@@ -109,6 +109,11 @@
109
109
  {
110
110
  "key": "P2",
111
111
  "title": "Pro",
112
+ "animeTone": "cool",
113
+ "strictDiffAnime": {
114
+ "monthly": "theme",
115
+ "yearly": "rainbow"
116
+ },
112
117
  "showBillingSubTitle": true,
113
118
  "features": [
114
119
  {
@@ -126,6 +131,11 @@
126
131
  {
127
132
  "key": "U3",
128
133
  "title": "Ultra",
134
+ "animeTone": "rainbow",
135
+ "strictDiffAnime": {
136
+ "monthly": "rainbow",
137
+ "yearly": "cool"
138
+ },
129
139
  "showBillingSubTitle": true,
130
140
  "titleTags": ["Most Popular"],
131
141
  "features": [
@@ -152,6 +162,7 @@
152
162
  {
153
163
  "key": "F1",
154
164
  "title": "Starter",
165
+ "animeTone": "mono",
155
166
  "subtitle": "25 Credits",
156
167
  "showBillingSubTitle": true,
157
168
  "features": [
@@ -164,6 +175,7 @@
164
175
  {
165
176
  "key": "P2",
166
177
  "title": "Popular",
178
+ "animeTone": "theme",
167
179
  "subtitle": "150 Credits",
168
180
  "showBillingSubTitle": true,
169
181
  "titleTags": ["Preferred"],
@@ -178,6 +190,7 @@
178
190
  {
179
191
  "key": "U3",
180
192
  "title": "Power",
193
+ "animeTone": "rainbow",
181
194
  "subtitle": "350 Credits",
182
195
  "showBillingSubTitle": true,
183
196
  "features": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windrun-huaiin/diaomao",
3
- "version": "30.0.0",
3
+ "version": "31.0.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -15,14 +15,14 @@
15
15
  "react"
16
16
  ],
17
17
  "dependencies": {
18
- "@clerk/nextjs": "^7.0.5",
18
+ "@clerk/nextjs": "^7.3.3",
19
19
  "@types/mdx": "^2.0.13",
20
- "@windrun-huaiin/backend-core": "30.0.0",
21
- "@windrun-huaiin/base-ui": "30.0.0",
22
- "@windrun-huaiin/contracts": "30.0.0",
23
- "@windrun-huaiin/fumadocs-local-md": "30.0.0",
24
- "@windrun-huaiin/lib": "30.0.0",
25
- "@windrun-huaiin/third-ui": "30.0.0",
20
+ "@windrun-huaiin/backend-core": "31.0.0",
21
+ "@windrun-huaiin/base-ui": "31.0.0",
22
+ "@windrun-huaiin/contracts": "31.0.0",
23
+ "@windrun-huaiin/fumadocs-local-md": "31.0.0",
24
+ "@windrun-huaiin/lib": "31.0.0",
25
+ "@windrun-huaiin/third-ui": "31.0.1",
26
26
  "clsx": "^2.1.1",
27
27
  "lucide-react": "^0.577.0",
28
28
  "next": "16.1.6",
@@ -44,7 +44,7 @@
44
44
  "@types/react": "^19.2.14",
45
45
  "@types/react-dom": "^19.2.3",
46
46
  "@typescript-eslint/parser": "^8.56.1",
47
- "@windrun-huaiin/dev-scripts": "^30.0.0",
47
+ "@windrun-huaiin/dev-scripts": "^31.0.0",
48
48
  "baseline-browser-mapping": "^2.10.0",
49
49
  "dotenv": "^17.4.2",
50
50
  "eslint": "^9.39.1",
@@ -1,20 +1,16 @@
1
1
  import { appConfig } from '@/lib/appConfig';
2
2
  import { siteDocs } from '@/lib/site-docs';
3
- import { NotFoundPage } from '@windrun-huaiin/base-ui/components';
4
3
  import { createFumaPage } from '@windrun-huaiin/third-ui/fuma/server/page-generator';
5
- import { SiteIcon } from '@/lib/site-config';
6
4
  import { LLMCopyButton } from '@windrun-huaiin/third-ui/fuma/mdx';
7
5
 
8
6
  const sourceKey = 'blog';
9
7
  const { Page, generateStaticParams, generateMetadata } = createFumaPage({
10
8
  sourceKey: sourceKey,
11
- mdxContentSource: () => siteDocs.getContentSource('blog'),
9
+ mdxContentSource: () => siteDocs.getContentSource(sourceKey),
12
10
  getMDXComponents: siteDocs.getMDXComponents,
13
11
  mdxSourceDir: appConfig.mdxSourceDir[sourceKey],
14
12
  githubBaseUrl: appConfig.githubBaseUrl,
15
13
  copyButtonComponent: <LLMCopyButton />,
16
- siteIcon: <SiteIcon />,
17
- FallbackPage: NotFoundPage,
18
14
  showBreadcrumb: false,
19
15
  showTableOfContent: true,
20
16
  showTableOfContentPopover: false,
@@ -1,13 +1,13 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import { baseOptions } from '@/app/[locale]/layout.config';
3
3
  import { levelNavLinks, primaryNavLinks } from '@/app/[locale]/layout.nav';
4
- import { showBanner, localePrefixAsNeeded, defaultLocale } from '@/lib/appConfig';
4
+ import { showBanner, localePrefixAsNeeded, defaultLocale, github } from '@/lib/appConfig';
5
+ import { i18n } from '@/lib/i18n-base';
5
6
  import { siteDocs } from '@/lib/site-docs';
6
7
  import { SiteDocsLayout } from '@windrun-huaiin/third-ui/fuma/base/site-docs-layout';
7
8
  import { SiteHomeLayout, type SiteHomeLayoutConfig } from '@windrun-huaiin/third-ui/fuma/base/site-home-layout';
8
9
  import { fingerprintConfig } from '@windrun-huaiin/backend-core/config/fingerprint';
9
10
  import { FingerprintProvider } from '@windrun-huaiin/third-ui/fingerprint';
10
- import { appConfig } from '@/lib/appConfig';
11
11
 
12
12
  async function contentOptions(locale: string): Promise<SiteHomeLayoutConfig> {
13
13
  return {
@@ -31,7 +31,8 @@ export default async function Layout({
31
31
  const contentLayoutOptions = await contentOptions(locale);
32
32
  const homeLayoutOptions: SiteHomeLayoutConfig = {
33
33
  ...contentLayoutOptions,
34
- githubUrl: appConfig.github,
34
+ i18n,
35
+ githubUrl: github,
35
36
  searchToggle: {
36
37
  enabled: false,
37
38
  },
@@ -1,23 +1,19 @@
1
1
  import { appConfig } from '@/lib/appConfig';
2
2
  import { siteDocs } from '@/lib/site-docs';
3
- import { SiteIcon } from '@/lib/site-config';
4
- import { NotFoundPage } from '@windrun-huaiin/base-ui/components';
5
3
  import { createFumaPage } from '@windrun-huaiin/third-ui/fuma/server/page-generator';
6
4
 
7
5
  const sourceKey = 'legal';
8
6
  const { Page, generateStaticParams, generateMetadata } = createFumaPage({
9
7
  sourceKey: sourceKey,
10
- mdxContentSource: () => siteDocs.getContentSource('legal'),
8
+ mdxContentSource: () => siteDocs.getContentSource(sourceKey),
11
9
  getMDXComponents: siteDocs.getMDXComponents,
12
10
  mdxSourceDir: appConfig.mdxSourceDir[sourceKey],
13
11
  githubBaseUrl: appConfig.githubBaseUrl,
14
- siteIcon: <SiteIcon />,
15
- FallbackPage: NotFoundPage,
16
12
  supportedLocales: appConfig.i18n.locales as string[],
17
13
  showBreadcrumb: false,
18
14
  showTableOfContent: true,
19
15
  showTableOfContentPopover: false,
20
- tocRenderMode: 'fumadocs-clerk'
16
+ tocRenderMode: 'fumadocs-normal'
21
17
  });
22
18
 
23
19
  export default Page;
@@ -1,11 +1,11 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import { baseOptions } from '@/app/[locale]/layout.config';
3
3
  import { levelNavLinks, primaryNavLinks } from '@/app/[locale]/layout.nav';
4
- import { showBanner, localePrefixAsNeeded, defaultLocale } from '@/lib/appConfig';
4
+ import { showBanner, localePrefixAsNeeded, defaultLocale, github } from '@/lib/appConfig';
5
+ import { i18n } from '@/lib/i18n-base';
5
6
  import { siteDocs } from '@/lib/site-docs';
6
7
  import { SiteDocsLayout } from '@windrun-huaiin/third-ui/fuma/base/site-docs-layout';
7
8
  import { SiteHomeLayout, type SiteHomeLayoutConfig } from '@windrun-huaiin/third-ui/fuma/base/site-home-layout';
8
- import { appConfig } from '@/lib/appConfig';
9
9
 
10
10
  async function contentOptions(locale: string): Promise<SiteHomeLayoutConfig> {
11
11
  return {
@@ -29,7 +29,8 @@ export default async function Layout({
29
29
  const contentLayoutOptions = await contentOptions(locale);
30
30
  const homeLayoutOptions: SiteHomeLayoutConfig = {
31
31
  ...contentLayoutOptions,
32
- githubUrl: appConfig.github,
32
+ i18n,
33
+ githubUrl: github,
33
34
  searchToggle: {
34
35
  enabled: false,
35
36
  },
@@ -1,8 +1,5 @@
1
- import { NotFoundPage } from '@windrun-huaiin/base-ui/components';
2
- import { SiteIcon } from '@/lib/site-config';
1
+ import { notFound } from 'next/navigation';
3
2
 
4
- export default function NotFound() {
5
- return (
6
- <NotFoundPage siteIcon={<SiteIcon />} />
7
- );
8
- }
3
+ export default function CatchAllPage() {
4
+ notFound();
5
+ }
@@ -1,4 +1,4 @@
1
- import { CreditPopoverClient } from '@/components/credit-popover-client';
1
+ import { CreditOverviewNavClient } from '@windrun-huaiin/third-ui/main/credit';
2
2
  import { appConfig } from '@/lib/appConfig';
3
3
  import { ClerkUser } from '@windrun-huaiin/third-ui/clerk/server';
4
4
  import type { SiteNavItemConfig } from '@windrun-huaiin/third-ui/fuma/base/site-layout-shared';
@@ -9,7 +9,12 @@ export async function homeHeavyItems(locale: string): Promise<SiteNavItemConfig[
9
9
  type: 'custom',
10
10
  secondary: true,
11
11
  mobilePinned: true,
12
- children: <CreditPopoverClient locale={locale} />,
12
+ children: (
13
+ <CreditOverviewNavClient
14
+ locale={locale}
15
+ endpoint="/api/user/credit-overview"
16
+ />
17
+ ),
13
18
  },
14
19
  {
15
20
  type: 'custom',
@@ -1,7 +1,7 @@
1
1
  import { baseOptions } from '@/app/[locale]/layout.config';
2
2
  import { levelNavLinks, primaryNavLinks } from '@/app/[locale]/layout.nav';
3
3
  import { homeHeavyItems } from './layout.heavy';
4
- import { showBanner, localePrefixAsNeeded, defaultLocale } from '@/lib/appConfig';
4
+ import { showBanner, localePrefixAsNeeded, defaultLocale, github } from '@/lib/appConfig';
5
5
  import { i18n } from '@/lib/i18n-base';
6
6
  import { fingerprintConfig } from '@windrun-huaiin/backend-core/config/fingerprint';
7
7
  import { FingerprintProvider } from '@windrun-huaiin/third-ui/fingerprint';
@@ -31,6 +31,7 @@ export default async function Layout({
31
31
  const homeLayoutOptions: SiteHomeLayoutConfig = {
32
32
  ...customeOptions,
33
33
  i18n,
34
+ githubUrl: github,
34
35
  searchToggle: {
35
36
  enabled: false,
36
37
  },
@@ -67,7 +67,7 @@ export async function levelNavLinks(locale: string): Promise<SiteNavItemConfig[]
67
67
  const blogsLinks: SiteMenuLeafConfig[] = [
68
68
  {
69
69
  text: 'async-architecture',
70
- description: '异步架构处理方案',
70
+ description: 'Async handler',
71
71
  path: '/blog/async-architecture',
72
72
  prefetch: false,
73
73
  icon: <T3PIcon />,
@@ -75,7 +75,7 @@ export async function levelNavLinks(locale: string): Promise<SiteNavItemConfig[]
75
75
  },
76
76
  {
77
77
  text: 'Config Sheet',
78
- description: '配置速查',
78
+ description: 'Quickstart config',
79
79
  path: '/blog/cheatsheet',
80
80
  prefetch: false,
81
81
  icon: <SettingsIcon />,
@@ -83,7 +83,7 @@ export async function levelNavLinks(locale: string): Promise<SiteNavItemConfig[]
83
83
  },
84
84
  {
85
85
  text: 'IOC',
86
- description: 'IOC统计',
86
+ description: 'IOC Analysis',
87
87
  path: '/blog/ioc',
88
88
  prefetch: false,
89
89
  icon: <ChartColumnStackedIcon />,
@@ -13,7 +13,6 @@ import React from 'react';
13
13
 
14
14
  export const dynamic = 'force-dynamic'
15
15
 
16
- // 网站元数据
17
16
  export async function generateMetadata({
18
17
  params: paramsPromise
19
18
  }: {
@@ -31,12 +30,12 @@ export async function generateMetadata({
31
30
 
32
31
  export default async function RootLayout({
33
32
  children,
34
- params: paramsPromise // 重命名参数
33
+ params: paramsPromise
35
34
  }: {
36
35
  children: React.ReactNode
37
36
  params: Promise<{ locale: string }>
38
37
  }) {
39
- const { locale } = await paramsPromise; // 使用新名称
38
+ const { locale } = await paramsPromise;
40
39
  setRequestLocale(locale);
41
40
  const messages = await getMessages();
42
41
  const fumaTranslations = await getFumaTranslations(locale);
@@ -0,0 +1,6 @@
1
+ import { SiteIcon } from '@/lib/site-config';
2
+ import { AnimeNotFoundPage } from '@windrun-huaiin/third-ui/main/anime';
3
+
4
+ export default function NotFound() {
5
+ return <AnimeNotFoundPage siteIcon={<SiteIcon />} />;
6
+ }
@@ -9,6 +9,7 @@ export const appConfig = {
9
9
  export const { isSupportedLocale, getValidLocale, generatedLocales } = createI18nHelpers(appConfig.i18n);
10
10
 
11
11
  export const { localePrefixAsNeeded, defaultLocale } = appConfig.i18n;
12
+ export const github = appConfig.github;
12
13
 
13
14
  // export shortcuts
14
15
  export const { iconColor, watermark, showBanner, clerkPageBanner, clerkAuthInModal, placeHolderImage } = appConfig.shortcuts;
package/src/lib/fonts.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import localFont from 'next/font/local';
2
2
 
3
- // 固定使用本地字体,不再依赖环境变量/远程 Google
3
+ // Just use local font,no more remote Google-font
4
4
  export const montserrat = localFont({
5
5
  src: [
6
6
  { path: '../../public/asserts/Montserrat-Regular.otf', weight: '400', style: 'normal' },
@@ -2,7 +2,7 @@
2
2
  title: Blog
3
3
  description: Articles and thoughts about various topics.
4
4
  icon: RssIcon
5
- date: 2026-05-11
5
+ date: 2026-05-14
6
6
  ---
7
7
 
8
8
  ## Past List
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: Monthly Summary
3
3
  description: Index and Summary
4
- date: 2026-05-11
4
+ date: 2026-05-14
5
5
  ---
6
6
 
7
7
 
package/src/proxy.ts CHANGED
@@ -12,7 +12,7 @@ const intlMiddleware = createMiddleware({
12
12
  localeDetection: false
13
13
  });
14
14
 
15
- // 需要身份认证的路由(页面路由)
15
+ // Page routes that require authentication.
16
16
  const protectedPageRoutes = createRouteMatcher(
17
17
  buildProtectedPageRoutePatterns(
18
18
  ['/dashboard', '/settings', '/profile', '/billing'],
@@ -20,25 +20,25 @@ const protectedPageRoutes = createRouteMatcher(
20
20
  )
21
21
  );
22
22
 
23
- // 需要身份认证的API路由
23
+ // API routes that require authentication.
24
24
  const protectedApiRoutes = createRouteMatcher([
25
- // Stripe支付相关API
25
+ // Stripe payment APIs.
26
26
  '/api/stripe(.*)',
27
- // 积分相关API
27
+ // Credit APIs.
28
28
  '/api/credit(.*)',
29
- // 交易记录API
29
+ // Transaction APIs.
30
30
  '/api/transaction(.*)'
31
31
  ]);
32
32
 
33
- // 免认证的API路由(webhook、匿名用户初始化等)
33
+ // Public API routes such as webhooks and anonymous user initialization.
34
34
  const publicApiRoutes = createRouteMatcher([
35
35
  // Stripe webhook
36
36
  '/api/webhook/stripe',
37
37
  // Clerk webhook
38
38
  '/api/webhook/clerk/user',
39
- // 匿名用户初始化
39
+ // Anonymous user initialization.
40
40
  '/api/user/anonymous/init',
41
- // 健康检查等
41
+ // Health checks and public content APIs.
42
42
  '/api/health',
43
43
  '/api/legal',
44
44
  '/api/docs',
@@ -46,8 +46,8 @@ const publicApiRoutes = createRouteMatcher([
46
46
  '/api/blog'
47
47
  ]);
48
48
 
49
- // v6 官方推荐写法:直接 export default clerkMiddleware(handler, options)
50
- // 完全不需要再包一层函数,也不需要手动 (req)
49
+ // Clerk v6 recommended usage: export clerkMiddleware(handler, options) directly.
50
+ // No extra wrapper function or manual request forwarding is needed.
51
51
  export default clerkMiddleware(
52
52
  async (auth, req: NextRequest) => {
53
53
  const { defaultLocale, locales } = appConfig.i18n;
@@ -72,8 +72,8 @@ export default clerkMiddleware(
72
72
  return authResponse;
73
73
  }
74
74
 
75
- // 对于无语言前缀的页面请求,根据配置进行处理
76
- // 避免落不到 [locale] 路由。
75
+ // Handle page requests without locale prefixes according to configuration.
76
+ // This prevents requests from missing the [locale] route.
77
77
  if (!hasLocalePrefix && !pathname.startsWith('/api/')) {
78
78
  const url = req.nextUrl.clone();
79
79
  url.pathname = `/${defaultLocale}${pathname}`;
@@ -85,7 +85,7 @@ export default clerkMiddleware(
85
85
  }
86
86
  }
87
87
 
88
- // 5. 其他路由使用默认的国际化中间件处理
88
+ // Use the default i18n middleware for all other routes.
89
89
 
90
90
  // handle trailing slash redirect
91
91
  if (req.nextUrl.pathname.length > 1 && req.nextUrl.pathname.endsWith("/")) {
@@ -94,12 +94,13 @@ export default clerkMiddleware(
94
94
  301
95
95
  );
96
96
  }
97
- // 默认处理其他路由(公开页面路由)
97
+ // Default handling for other public page routes.
98
98
  return intlMiddleware(req);
99
99
  },
100
100
  { debug: appConfig.clerk.debug }
101
101
  );
102
102
 
103
+
103
104
  export const config = {
104
105
  matcher: [
105
106
  // Skip Next.js internals and all static files, but include API routes
@@ -1,72 +0,0 @@
1
- 'use client';
2
-
3
- import { CreditNavButton, CreditOverviewClient } from '@windrun-huaiin/third-ui/main/credit';
4
- import type { CreditOverviewData, CreditOverviewTranslations } from '@windrun-huaiin/third-ui/main/credit';
5
- import { useEffect, useState } from 'react';
6
-
7
- interface CreditPopoverClientProps {
8
- locale: string;
9
- }
10
-
11
- interface CreditOverviewPayload {
12
- data: CreditOverviewData;
13
- totalLabel: string;
14
- translations: CreditOverviewTranslations;
15
- }
16
-
17
- export function CreditPopoverClient({ locale }: CreditPopoverClientProps) {
18
- const [payload, setPayload] = useState<CreditOverviewPayload | null>(null);
19
-
20
- useEffect(() => {
21
- const controller = new AbortController();
22
-
23
- async function loadCreditOverview() {
24
- try {
25
- const response = await fetch(
26
- `/api/user/credit-overview?locale=${encodeURIComponent(locale)}`,
27
- {
28
- credentials: 'same-origin',
29
- signal: controller.signal,
30
- },
31
- );
32
-
33
- if (!response.ok) {
34
- return;
35
- }
36
-
37
- const nextPayload = (await response.json()) as CreditOverviewPayload | null;
38
- if (!controller.signal.aborted) {
39
- setPayload(nextPayload);
40
- }
41
- } catch (error) {
42
- if (!controller.signal.aborted) {
43
- console.warn('[CreditPopover] Failed to load credit overview', error);
44
- }
45
- }
46
- }
47
-
48
- loadCreditOverview();
49
-
50
- return () => {
51
- controller.abort();
52
- };
53
- }, [locale]);
54
-
55
- if (!payload) {
56
- return null;
57
- }
58
-
59
- return (
60
- <CreditNavButton
61
- locale={locale}
62
- totalBalance={payload.data.totalBalance}
63
- totalLabel={payload.totalLabel}
64
- >
65
- <CreditOverviewClient
66
- locale={locale}
67
- data={payload.data}
68
- translations={payload.translations}
69
- />
70
- </CreditNavButton>
71
- );
72
- }
@@ -1,136 +0,0 @@
1
- import '@/server/prisma';
2
- import { creditService, subscriptionService } from '@windrun-huaiin/backend-core/database';
3
- import { getOptionalServerAuthUser } from '@windrun-huaiin/backend-core/auth/server';
4
- import { viewLocalTime } from '@windrun-huaiin/lib/utils';
5
- import { CreditNavButton } from '@windrun-huaiin/third-ui/main/credit';
6
- import type { CreditOverviewData } from '@windrun-huaiin/third-ui/main/credit/server';
7
- import { CreditOverview } from '@windrun-huaiin/third-ui/main/credit/server';
8
- import { buildMoneyPriceData } from '@windrun-huaiin/third-ui/main/money-price/server';
9
- import { moneyPriceConfig } from '@windrun-huaiin/backend-core/config/money-price';
10
- import { buildInitUserContextFromEntities } from '@windrun-huaiin/backend-core/context'
11
- import { getTranslations } from 'next-intl/server';
12
- import { getAsNeededLocalizedUrl } from '@windrun-huaiin/lib/utils';
13
- import { localePrefixAsNeeded, defaultLocale } from '@/lib/appConfig';
14
-
15
- interface CreditPopoverProps {
16
- locale: string;
17
- }
18
-
19
- export async function CreditPopover({ locale }: CreditPopoverProps) {
20
- const authUser = await getOptionalServerAuthUser();
21
- if (!authUser) {
22
- return null;
23
- }
24
- const { user } = authUser;
25
-
26
- const enableSubscriptionUpgrade = process.env.ENABLE_STRIPE_SUBSCRIPTION_UPGRADE !== 'false';
27
-
28
- const [credit, subscription, t, moneyPriceData] = await Promise.all([
29
- creditService.getCredit(user.userId),
30
- subscriptionService.getActiveSubscription(user.userId),
31
- getTranslations({ locale, namespace: 'credit' }),
32
- buildMoneyPriceData({
33
- locale,
34
- currency: moneyPriceConfig.display.currency,
35
- enabledBillingTypes: ['monthly', 'yearly', 'onetime'],
36
- }),
37
- ]);
38
-
39
- if (!credit) {
40
- return null;
41
- }
42
-
43
- const initUserContext = buildInitUserContextFromEntities({
44
- user,
45
- credit,
46
- subscription,
47
- });
48
-
49
- const totalBalance =
50
- (credit.balanceFree ?? 0) +
51
- (credit.balancePaid ?? 0) +
52
- (credit.balanceOneTimePaid ?? 0);
53
-
54
- // 根据是否订阅,动态调整 buckets 顺序
55
- // 已订阅:subscription → onetime → free
56
- // 未订阅:onetime → free
57
- // 为0的类型积分不展示
58
-
59
- // 直接基于 credit 对象生成 buckets,无需额外传参
60
- const buckets = [
61
- ...(credit.balancePaid > 0
62
- ? [{
63
- kind: 'subscription' as const,
64
- balance: credit.balancePaid,
65
- limit: credit.totalPaidLimit,
66
- expiresAt: viewLocalTime(credit.paidEnd)
67
- }]
68
- : []),
69
-
70
- ...(credit.balanceOneTimePaid > 0
71
- ? [{
72
- kind: 'onetime' as const,
73
- balance: credit.balanceOneTimePaid,
74
- limit: credit.totalOneTimePaidLimit,
75
- expiresAt: viewLocalTime(credit.oneTimePaidEnd)
76
- }]
77
- : []),
78
-
79
- ...(credit.balanceFree > 0
80
- ? [{
81
- kind: 'free' as const,
82
- balance: credit.balanceFree,
83
- limit: credit.totalFreeLimit,
84
- expiresAt: viewLocalTime(credit.freeEnd)
85
- }]
86
- : [])
87
- ];
88
-
89
- // 按照项目设置来决定是否带上语言前缀
90
- const pricingPageBaseUrl = getAsNeededLocalizedUrl(locale, "/pricing", localePrefixAsNeeded, defaultLocale);
91
-
92
- const data: CreditOverviewData = {
93
- totalBalance,
94
- buckets,
95
- pricingContext: {
96
- moneyPriceData,
97
- moneyPriceConfig,
98
- checkoutApiEndpoint: '/api/stripe/checkout',
99
- customerPortalApiEndpoint: '/api/stripe/customer-portal',
100
- enableSubscriptionUpgrade,
101
- initUserContext,
102
- },
103
- ctaBehaviors: {
104
- subscribe: {
105
- desktop: { kind: 'modal', mode: 'subscription' },
106
- mobile: { kind: 'redirect', url: `${pricingPageBaseUrl}?initialBillingType=subscription` },
107
- },
108
- manage: {
109
- desktop: { kind: 'auth' },
110
- mobile: { kind: 'auth' },
111
- },
112
- onetime: {
113
- desktop: { kind: 'modal', mode: 'onetime' },
114
- mobile: { kind: 'redirect', url: `${pricingPageBaseUrl}?initialBillingType=onetime` },
115
- },
116
- },
117
- };
118
-
119
- if (subscription) {
120
- data.subscription = {
121
- planName: subscription.priceName ?? '',
122
- periodStart: viewLocalTime(subscription.subPeriodStart),
123
- periodEnd: viewLocalTime(subscription.subPeriodEnd),
124
- };
125
- }
126
-
127
- return (
128
- <CreditNavButton
129
- locale={locale}
130
- totalBalance={totalBalance}
131
- totalLabel={t('summary.totalLabel')}
132
- >
133
- <CreditOverview locale={locale} data={data} />
134
- </CreditNavButton>
135
- );
136
- }