Skip to content

Comments

feat: 新增cdk兑换页面(没测#2

Merged
MistEO merged 4 commits intomasterfrom
feat/exchange
Dec 31, 2025
Merged

feat: 新增cdk兑换页面(没测#2
MistEO merged 4 commits intomasterfrom
feat/exchange

Conversation

@MistEO
Copy link
Contributor

@MistEO MistEO commented Dec 31, 2025

Summary by Sourcery

添加一个专用的 CDK 兑换页面,并增强主页演示,加入交互式模拟与下载流程。

新功能:

  • 新增用于 CDK 兑换的 /exchange 页面,支持本地化、主题定制、API 集成以及兑换成功弹窗界面。
  • 在主页 hero 区添加 CDK 兑换入口,链接至新的兑换页面。
  • 在主页演示区域实现交互式命令模拟控制台,包括分步输出、打字动画以及可选的下载重定向。

功能改进:

  • 通过在模拟期间禁用控件、显示进度状态以及按条件渲染内容,改善演示输入和示例命令的用户体验。
  • 禁用装饰性渐变边框的指针交互,避免影响底层 UI 元素的正常操作。
Original summary in English

Summary by Sourcery

Add a dedicated CDK exchange page and enhance the homepage demo with an interactive simulation and download flow.

New Features:

  • Introduce a new /exchange page for CDK redemption with localization, theming, API integration, and success modal UI.
  • Add a CDK exchange entry point to the homepage hero that links to the new redemption page.
  • Implement an interactive command simulation console in the homepage demo section, including stepwise output, typing animation, and optional download redirect.

Enhancements:

  • Improve the demo input and example commands UX by disabling controls during simulation, showing progress states, and conditionally rendering content.
  • Prevent pointer interaction with decorative gradient borders to avoid interfering with underlying UI elements.
Original summary in English

Summary by Sourcery

添加一个专用的 CDK 兑换页面,并增强主页演示,加入交互式模拟与下载流程。

新功能:

  • 新增用于 CDK 兑换的 /exchange 页面,支持本地化、主题定制、API 集成以及兑换成功弹窗界面。
  • 在主页 hero 区添加 CDK 兑换入口,链接至新的兑换页面。
  • 在主页演示区域实现交互式命令模拟控制台,包括分步输出、打字动画以及可选的下载重定向。

功能改进:

  • 通过在模拟期间禁用控件、显示进度状态以及按条件渲染内容,改善演示输入和示例命令的用户体验。
  • 禁用装饰性渐变边框的指针交互,避免影响底层 UI 元素的正常操作。
Original summary in English

Summary by Sourcery

Add a dedicated CDK exchange page and enhance the homepage demo with an interactive simulation and download flow.

New Features:

  • Introduce a new /exchange page for CDK redemption with localization, theming, API integration, and success modal UI.
  • Add a CDK exchange entry point to the homepage hero that links to the new redemption page.
  • Implement an interactive command simulation console in the homepage demo section, including stepwise output, typing animation, and optional download redirect.

Enhancements:

  • Improve the demo input and example commands UX by disabling controls during simulation, showing progress states, and conditionally rendering content.
  • Prevent pointer interaction with decorative gradient borders to avoid interfering with underlying UI elements.

@MistEO MistEO marked this pull request as ready for review December 31, 2025 09:43
Copilot AI review requested due to automatic review settings December 31, 2025 09:43
@MistEO MistEO merged commit e2ba55f into master Dec 31, 2025
3 checks passed
@MistEO MistEO deleted the feat/exchange branch December 31, 2025 09:43
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey,我发现了 3 个问题,并给出了一些整体性反馈:

  • 现在 .gradient-border 类上添加了 pointer-events: none,这会导致容器内部的所有点击/输入都失效(例如演示中的输入框和按钮);建议只将 pointer-events: none 应用于装饰性的伪元素,或者单独的遮罩层容器,而不是内容容器本身。
  • Home 组件中的 typeText 函数创建了一个 interval,但在组件卸载或者新一轮模拟开始时从未被清理,这可能导致内存泄漏以及在已卸载组件上更新状态;建议用 ref 记录该 interval,并在清理 effect 中或在开始新的模拟前清除它。
  • 主题/语言环境(locale)处理以及部分图标在 app/page.tsxapp/exchange/page.tsx 之间存在重复;建议抽取共享的 hooks/组件(例如带主题/语言切换的布局头部),以减少重复并保持各页面行为一致。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `.gradient-border` class now has `pointer-events: none`, which will prevent all clicks/inputs inside those containers (e.g., the demo input and buttons) from working; consider applying `pointer-events: none` only to the decorative pseudo-element or a separate overlay wrapper instead of the content container.
- The `typeText` function in `Home` creates an interval that is never cleared on unmount or when a new simulation starts, which can cause memory leaks and state updates on unmounted components; consider tracking the interval with a ref and clearing it in a cleanup effect or before starting a new simulation.
- The theme/locale handling and several icons are duplicated between `app/page.tsx` and `app/exchange/page.tsx`; consider extracting shared hooks/components (e.g., a layout header with theme/locale toggles) to reduce duplication and keep behavior consistent across pages.

## Individual Comments

### Comment 1
<location> `app/globals.css:100` </location>
<code_context>
   mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
   -webkit-mask-composite: xor;
   mask-composite: exclude;
+  pointer-events: none;
 }

</code_context>

<issue_to_address>
**issue (bug_risk):** Using `pointer-events: none` on `.gradient-border` likely disables interaction for its children.

On HTML elements, `pointer-events: none` also disables pointer events for all descendants, so wrapping inputs/buttons in `.gradient-border` will likely make them unclickable. If you just want the decorative border to ignore pointer events, move that styling to a pseudo-element (e.g. `.gradient-border::before`) with `pointer-events: none` and keep the main container interactive.
</issue_to_address>

### Comment 2
<location> `app/page.tsx:182-191` </location>
<code_context>
   };

+  // 流式输出单行文字
+  const typeText = (text: string, onComplete: () => void) => {
+    let index = 0;
+    setCurrentTypingText("");
+    const interval = setInterval(() => {
+      if (index < text.length) {
+        setCurrentTypingText(text.slice(0, index + 1));
+        index++;
+      } else {
+        clearInterval(interval);
+        onComplete();
+      }
+    }, 30 + Math.random() * 20); // 随机延迟模拟真实打字效果
+    return interval;
+  };
+
</code_context>

<issue_to_address>
**issue (bug_risk):** The typing simulation interval is never cleaned up on component unmount.

The interval created in `typeText` is only cleared when typing completes. If the component unmounts mid-typing (e.g. during navigation), it can keep running and call `setState` on an unmounted component. Please store the interval id in a ref and clear it in a `useEffect` cleanup, or refactor to a `setTimeout` + effect pattern that automatically cancels on unmount.
</issue_to_address>

### Comment 3
<location> `app/page.tsx:240-248` </location>
<code_context>
+    setIsSimulating(false);
+
+    // 自动跳转到下载页面
+    if (DOWNLOAD_URL) {
+      setTimeout(() => {
+        window.open(DOWNLOAD_URL, "_blank");
+      }, 1000);
+    }
</code_context>

<issue_to_address>
**🚨 suggestion (security):** Opening a new window without `noopener`/`noreferrer` can introduce a security risk.

`window.open(DOWNLOAD_URL, "_blank")` lets the new tab access and potentially modify `window.opener`. To mitigate reverse tabnabbing, use `window.open(DOWNLOAD_URL, "_blank", "noopener,noreferrer")` or set `newWindow.opener = null` after opening.

```suggestion
    setSimulationComplete(true);
    setIsSimulating(false);

    // 自动跳转到下载页面
    if (DOWNLOAD_URL) {
      setTimeout(() => {
        const newWindow = window.open(DOWNLOAD_URL, "_blank", "noopener,noreferrer");
        if (newWindow) {
          newWindow.opener = null;
        }
      }, 1000);
    }
```
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得这些评审有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进后续评审。
Original comment in English

Hey - I've found 3 issues, and left some high level feedback:

  • The .gradient-border class now has pointer-events: none, which will prevent all clicks/inputs inside those containers (e.g., the demo input and buttons) from working; consider applying pointer-events: none only to the decorative pseudo-element or a separate overlay wrapper instead of the content container.
  • The typeText function in Home creates an interval that is never cleared on unmount or when a new simulation starts, which can cause memory leaks and state updates on unmounted components; consider tracking the interval with a ref and clearing it in a cleanup effect or before starting a new simulation.
  • The theme/locale handling and several icons are duplicated between app/page.tsx and app/exchange/page.tsx; consider extracting shared hooks/components (e.g., a layout header with theme/locale toggles) to reduce duplication and keep behavior consistent across pages.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `.gradient-border` class now has `pointer-events: none`, which will prevent all clicks/inputs inside those containers (e.g., the demo input and buttons) from working; consider applying `pointer-events: none` only to the decorative pseudo-element or a separate overlay wrapper instead of the content container.
- The `typeText` function in `Home` creates an interval that is never cleared on unmount or when a new simulation starts, which can cause memory leaks and state updates on unmounted components; consider tracking the interval with a ref and clearing it in a cleanup effect or before starting a new simulation.
- The theme/locale handling and several icons are duplicated between `app/page.tsx` and `app/exchange/page.tsx`; consider extracting shared hooks/components (e.g., a layout header with theme/locale toggles) to reduce duplication and keep behavior consistent across pages.

## Individual Comments

### Comment 1
<location> `app/globals.css:100` </location>
<code_context>
   mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
   -webkit-mask-composite: xor;
   mask-composite: exclude;
+  pointer-events: none;
 }

</code_context>

<issue_to_address>
**issue (bug_risk):** Using `pointer-events: none` on `.gradient-border` likely disables interaction for its children.

On HTML elements, `pointer-events: none` also disables pointer events for all descendants, so wrapping inputs/buttons in `.gradient-border` will likely make them unclickable. If you just want the decorative border to ignore pointer events, move that styling to a pseudo-element (e.g. `.gradient-border::before`) with `pointer-events: none` and keep the main container interactive.
</issue_to_address>

### Comment 2
<location> `app/page.tsx:182-191` </location>
<code_context>
   };

+  // 流式输出单行文字
+  const typeText = (text: string, onComplete: () => void) => {
+    let index = 0;
+    setCurrentTypingText("");
+    const interval = setInterval(() => {
+      if (index < text.length) {
+        setCurrentTypingText(text.slice(0, index + 1));
+        index++;
+      } else {
+        clearInterval(interval);
+        onComplete();
+      }
+    }, 30 + Math.random() * 20); // 随机延迟模拟真实打字效果
+    return interval;
+  };
+
</code_context>

<issue_to_address>
**issue (bug_risk):** The typing simulation interval is never cleaned up on component unmount.

The interval created in `typeText` is only cleared when typing completes. If the component unmounts mid-typing (e.g. during navigation), it can keep running and call `setState` on an unmounted component. Please store the interval id in a ref and clear it in a `useEffect` cleanup, or refactor to a `setTimeout` + effect pattern that automatically cancels on unmount.
</issue_to_address>

### Comment 3
<location> `app/page.tsx:240-248` </location>
<code_context>
+    setIsSimulating(false);
+
+    // 自动跳转到下载页面
+    if (DOWNLOAD_URL) {
+      setTimeout(() => {
+        window.open(DOWNLOAD_URL, "_blank");
+      }, 1000);
+    }
</code_context>

<issue_to_address>
**🚨 suggestion (security):** Opening a new window without `noopener`/`noreferrer` can introduce a security risk.

`window.open(DOWNLOAD_URL, "_blank")` lets the new tab access and potentially modify `window.opener`. To mitigate reverse tabnabbing, use `window.open(DOWNLOAD_URL, "_blank", "noopener,noreferrer")` or set `newWindow.opener = null` after opening.

```suggestion
    setSimulationComplete(true);
    setIsSimulating(false);

    // 自动跳转到下载页面
    if (DOWNLOAD_URL) {
      setTimeout(() => {
        const newWindow = window.open(DOWNLOAD_URL, "_blank", "noopener,noreferrer");
        if (newWindow) {
          newWindow.opener = null;
        }
      }, 1000);
    }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk):.gradient-border 上使用 pointer-events: none 很可能会禁用其子元素的交互。

在 HTML 元素上,pointer-events: none 同样会禁用所有后代元素的指针事件,因此将输入框/按钮包裹在 .gradient-border 中,很可能会导致它们无法点击。如果你只是希望装饰性的边框不响应指针事件,可以把这个样式移动到伪元素上(例如 .gradient-border::before)并设置 pointer-events: none,同时保持主容器仍然可交互。

Original comment in English

issue (bug_risk): Using pointer-events: none on .gradient-border likely disables interaction for its children.

On HTML elements, pointer-events: none also disables pointer events for all descendants, so wrapping inputs/buttons in .gradient-border will likely make them unclickable. If you just want the decorative border to ignore pointer events, move that styling to a pseudo-element (e.g. .gradient-border::before) with pointer-events: none and keep the main container interactive.

Comment on lines +182 to +191
const typeText = (text: string, onComplete: () => void) => {
let index = 0;
setCurrentTypingText("");
const interval = setInterval(() => {
if (index < text.length) {
setCurrentTypingText(text.slice(0, index + 1));
index++;
} else {
clearInterval(interval);
onComplete();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): 打字模拟用到的 interval 在组件卸载时从未被清理。

typeText 中创建的 interval 只会在打字完成时被清除。如果组件在打字过程中卸载(例如导航到其他页面),该 interval 仍会继续运行,并在已卸载组件上调用 setState。请将 interval 的 id 存在一个 ref 中,并在 useEffect 的清理函数中清除它,或者重构为 setTimeout + effect 的模式,使其在组件卸载时自动取消。

Original comment in English

issue (bug_risk): The typing simulation interval is never cleaned up on component unmount.

The interval created in typeText is only cleared when typing completes. If the component unmounts mid-typing (e.g. during navigation), it can keep running and call setState on an unmounted component. Please store the interval id in a ref and clear it in a useEffect cleanup, or refactor to a setTimeout + effect pattern that automatically cancels on unmount.

Comment on lines +240 to +248
setSimulationComplete(true);
setIsSimulating(false);

// 自动跳转到下载页面
if (DOWNLOAD_URL) {
setTimeout(() => {
window.open(DOWNLOAD_URL, "_blank");
}, 1000);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 suggestion (security): 在没有使用 noopener/noreferrer 的情况下打开新窗口会带来安全风险。

window.open(DOWNLOAD_URL, "_blank") 允许新标签访问并潜在地修改 window.opener。为了缓解反向标签劫持(reverse tabnabbing)风险,请使用 window.open(DOWNLOAD_URL, "_blank", "noopener,noreferrer"),或者在打开之后设置 newWindow.opener = null

Suggested change
setSimulationComplete(true);
setIsSimulating(false);
// 自动跳转到下载页面
if (DOWNLOAD_URL) {
setTimeout(() => {
window.open(DOWNLOAD_URL, "_blank");
}, 1000);
}
setSimulationComplete(true);
setIsSimulating(false);
// 自动跳转到下载页面
if (DOWNLOAD_URL) {
setTimeout(() => {
const newWindow = window.open(DOWNLOAD_URL, "_blank", "noopener,noreferrer");
if (newWindow) {
newWindow.opener = null;
}
}, 1000);
}
Original comment in English

🚨 suggestion (security): Opening a new window without noopener/noreferrer can introduce a security risk.

window.open(DOWNLOAD_URL, "_blank") lets the new tab access and potentially modify window.opener. To mitigate reverse tabnabbing, use window.open(DOWNLOAD_URL, "_blank", "noopener,noreferrer") or set newWindow.opener = null after opening.

Suggested change
setSimulationComplete(true);
setIsSimulating(false);
// 自动跳转到下载页面
if (DOWNLOAD_URL) {
setTimeout(() => {
window.open(DOWNLOAD_URL, "_blank");
}, 1000);
}
setSimulationComplete(true);
setIsSimulating(false);
// 自动跳转到下载页面
if (DOWNLOAD_URL) {
setTimeout(() => {
const newWindow = window.open(DOWNLOAD_URL, "_blank", "noopener,noreferrer");
if (newWindow) {
newWindow.opener = null;
}
}, 1000);
}

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new CDK (activation code) exchange page that allows users to redeem codes for trial access. The implementation includes full localization support (English and Chinese), theme toggling, backend API integration, and an interactive demo simulation on the home page.

Key Changes:

  • New /exchange page with form validation and API integration for CDK redemption
  • Enhanced home page demo with terminal-style simulation showing browser limitations
  • Updated navigation to prioritize CDK exchange over GitHub repository link

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 21 comments.

Show a summary per file
File Description
locales/zh.json Added Chinese translations for exchange page and demo simulation messages
locales/en.json Added English translations for exchange page and demo simulation messages
app/page.tsx Added gift icon, simulation logic with typing effects, replaced primary CTA with exchange link
app/globals.css Added pointer-events: none to gradient borders to prevent interaction issues
app/exchange/page.tsx New page implementing CDK exchange functionality with modal success dialog

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +310 to +376
{showModal && result && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm" onClick={closeModal}></div>
<div className="relative bg-surface rounded-2xl p-8 max-w-md w-full shadow-2xl border border-border">
<button
onClick={closeModal}
className="absolute top-4 right-4 p-2 rounded-lg hover:bg-surface-light transition-colors text-foreground/50 hover:text-foreground"
>
<CloseIcon />
</button>

<div className="text-center mb-6">
<div className="w-16 h-16 rounded-full bg-green-500/10 flex items-center justify-center mx-auto mb-4">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="w-8 h-8 text-green-500">
<polyline points="20 6 9 17 4 12"/>
</svg>
</div>
<h2 className="text-2xl font-bold text-foreground">{t.exchange.successTitle}</h2>
</div>

<div className="space-y-4">
{/* New CDK */}
<div className="p-4 rounded-lg bg-surface-light border border-border">
<div className="text-sm text-foreground/50 mb-2">{t.exchange.newCdk}</div>
<div className="flex items-center gap-2">
<code className="flex-1 font-mono text-sm text-accent break-all">{result.new_cdk}</code>
<button
onClick={handleCopy}
className="p-2 rounded-lg hover:bg-surface transition-colors text-foreground/50 hover:text-foreground flex-shrink-0"
title={t.exchange.copy}
>
{copied ? <CheckIcon /> : <CopyIcon />}
</button>
</div>
{copied && (
<div className="text-xs text-green-500 mt-2">{t.exchange.copySuccess}</div>
)}
</div>

{/* Stats */}
<div className="grid grid-cols-2 gap-4">
<div className="p-4 rounded-lg bg-surface-light border border-border text-center">
<div className="text-sm text-foreground/50 mb-1">{t.exchange.points}</div>
<div className="text-2xl font-bold text-accent">{result.points}</div>
</div>
<div className="p-4 rounded-lg bg-surface-light border border-border text-center">
<div className="text-sm text-foreground/50 mb-1">{t.exchange.remainingDays}</div>
<div className="text-2xl font-bold text-foreground">{result.remaining_days}</div>
</div>
</div>

{/* Expiry */}
<div className="p-4 rounded-lg bg-surface-light border border-border">
<div className="text-sm text-foreground/50 mb-1">{t.exchange.expiredAt}</div>
<div className="text-foreground font-medium">{result.expired_at}</div>
</div>

<button
onClick={closeModal}
className="w-full btn-secondary py-3"
>
{t.exchange.close}
</button>
</div>
</div>
</div>
)}
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The modal backdrop does not trap focus within the modal dialog. When the modal is open, users can still tab to elements behind it. Consider adding focus trap functionality to ensure keyboard navigation stays within the modal, and set focus to the first interactive element when the modal opens. Also, the close button and backdrop should handle Escape key press for better accessibility.

Copilot uses AI. Check for mistakes.
Comment on lines +282 to +286
/>
</div>

{error && (
<div className="p-4 rounded-lg bg-red-500/10 border border-red-500/30 text-red-500 text-sm text-center">
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The input field lacks proper validation feedback for accessibility. When an error occurs, the error message should be associated with the input using aria-describedby to ensure screen reader users are informed of the validation error. Consider adding an id to the error div and referencing it in the input's aria-describedby attribute.

Suggested change
/>
</div>
{error && (
<div className="p-4 rounded-lg bg-red-500/10 border border-red-500/30 text-red-500 text-sm text-center">
aria-invalid={!!error}
aria-describedby={error ? "cdk-error" : undefined}
/>
</div>
{error && (
<div
id="cdk-error"
className="p-4 rounded-lg bg-red-500/10 border border-red-500/30 text-red-500 text-sm text-center"
>

Copilot uses AI. Check for mistakes.
</div>
)}

{/* 示例命令列表 */}
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is in Chinese. Should be translated to English: "// Example command list"

Suggested change
{/* 示例命令列表 */}
{/* Example command list */}

Copilot uses AI. Check for mistakes.
return;
}

//验证 CDK 格式(20-32位字母数字)
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is in Chinese. Should be translated to English: "// Validate CDK format (20-32 alphanumeric characters)"

Suggested change
//验证 CDK 格式(20-32位字母数字)
// Validate CDK format (20-32 alphanumeric characters)

Copilot uses AI. Check for mistakes.
Comment on lines +211 to +219
// Fallback for older browsers
const textArea = document.createElement("textarea");
textArea.value = result.new_cdk;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using document.execCommand('copy') is deprecated and may not work in all modern browsers. Since you already have a try-catch block with navigator.clipboard.writeText, the fallback should handle the error more gracefully or remove this deprecated approach entirely. Consider showing an error message to the user if clipboard access fails.

Suggested change
// Fallback for older browsers
const textArea = document.createElement("textarea");
textArea.value = result.new_cdk;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
// Handle clipboard access failure gracefully without using deprecated APIs
setError(
(t.exchange?.errors &&
(t.exchange.errors.clipboardFailed ||
t.exchange.errors.generic)) ||
"Failed to copy to clipboard. Please copy the code manually."
);

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +15
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "https://api.example.com";

Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API_BASE_URL fallback value "https://api.example.com" is a non-functional placeholder domain. This will cause all API requests to fail if the environment variable is not set. Consider using a more descriptive error or at least documenting that NEXT_PUBLIC_API_BASE_URL must be configured before deployment.

Suggested change
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "https://api.example.com";
const API_BASE_URL_ENV = process.env.NEXT_PUBLIC_API_BASE_URL;
if (!API_BASE_URL_ENV) {
throw new Error(
"NEXT_PUBLIC_API_BASE_URL is not set. Please configure this environment variable before deploying the exchange page."
);
}
const API_BASE_URL = API_BASE_URL_ENV;

Copilot uses AI. Check for mistakes.
Comment on lines +302 to +305
<a href="/exchange" className="btn-primary inline-flex items-center justify-center gap-2">
<GiftIcon />
{t.hero.exchangeCdk}
</a>
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The primary call-to-action button in the hero section was changed from linking to the GitHub repository (https://github.com/MaaXYZ/MaaMCP) to the exchange page (/exchange). This significantly changes the user journey - the original "Get Started" action directed users to the main project repository, while now it directs to a CDK exchange page. This may not align with the primary goal for first-time visitors who want to learn about or use the product. Consider keeping the GitHub link as the primary CTA and making the exchange link secondary, or adding both options.

Suggested change
<a href="/exchange" className="btn-primary inline-flex items-center justify-center gap-2">
<GiftIcon />
{t.hero.exchangeCdk}
</a>
<a href="https://github.com/MaaXYZ/MaaMCP" className="btn-primary inline-flex items-center justify-center gap-2">
<GiftIcon />
{t.hero.exchangeCdk}
</a>
<a href="/exchange" className="btn-secondary inline-flex items-center justify-center gap-2">
<GiftIcon />
{t.hero.exchangeCdk}
</a>

Copilot uses AI. Check for mistakes.
{useCases.map((useCase, index) => (
<button
key={index}
onClick={() => {
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The runSimulation function can be triggered multiple times by clicking example commands even when a simulation is already running, due to the check happening after the click handler sets the command. The example buttons at lines 458-472 call runSimulation(useCase.command) directly but the disabled state only prevents clicking the same button again, not starting a new simulation from a different example button. Consider checking isSimulating before calling runSimulation in the example button handlers.

Suggested change
onClick={() => {
onClick={() => {
if (isSimulating) {
return;
}

Copilot uses AI. Check for mistakes.
clearInterval(interval);
onComplete();
}
}, 30 + Math.random() * 20); // 随机延迟模拟真实打字效果
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typing delay uses 30 + Math.random() * 20 which generates a new random value on every character. This creates inconsistent timing between characters within the same line. For a more realistic typing effect, consider generating the delay once per line or using a consistent delay value. The current implementation also makes the typing speed unpredictable for testing purposes.

Copilot uses AI. Check for mistakes.
return "en";
}

// 下载链接(待填写)
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is in Chinese while the codebase appears to use English for comments. This should be translated to English for consistency: "// Download link (to be filled in)"

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant