feat: 反馈前需登录

This commit is contained in:
hzm
2026-03-09 20:46:59 +08:00
parent c3515c7011
commit 750e72823b
5 changed files with 94 additions and 2 deletions

View File

@@ -1,3 +1,4 @@
import { Toaster } from '@/components/ui/sonner';
import './globals.css'; import './globals.css';
import AnalyticsGate from './components/AnalyticsGate'; import AnalyticsGate from './components/AnalyticsGate';
import packageJson from '../package.json'; import packageJson from '../package.json';
@@ -29,6 +30,7 @@ export default function RootLayout({ children }) {
<body> <body>
<AnalyticsGate GA_ID={GA_ID} /> <AnalyticsGate GA_ID={GA_ID} />
{children} {children}
<Toaster />
</body> </body>
</html> </html>
); );

View File

@@ -61,6 +61,7 @@ import WeChatModal from "./components/WeChatModal";
import DcaModal from "./components/DcaModal"; import DcaModal from "./components/DcaModal";
import githubImg from "./assets/github.svg"; import githubImg from "./assets/github.svg";
import { supabase, isSupabaseConfigured } from './lib/supabase'; import { supabase, isSupabaseConfigured } from './lib/supabase';
import { toast as sonnerToast } from 'sonner';
import { recordValuation, getAllValuationSeries, clearFund } from './lib/valuationTimeseries'; import { recordValuation, getAllValuationSeries, clearFund } from './lib/valuationTimeseries';
import { loadHolidaysForYears, isTradingDay as isDateTradingDay } from './lib/tradingCalendar'; import { loadHolidaysForYears, isTradingDay as isDateTradingDay } from './lib/tradingCalendar';
import { parseFundTextWithLLM, fetchFundData, fetchLatestRelease, fetchShanghaiIndexDate, fetchSmartFundNetValue, searchFunds } from './api/fund'; import { parseFundTextWithLLM, fetchFundData, fetchLatestRelease, fetchShanghaiIndexDate, fetchSmartFundNetValue, searchFunds } from './api/fund';
@@ -4216,6 +4217,10 @@ export default function HomePage() {
<button <button
className="link-button" className="link-button"
onClick={() => { onClick={() => {
if (!user?.id) {
sonnerToast.error('请先登录后再提交反馈');
return;
}
setFeedbackNonce((n) => n + 1); setFeedbackNonce((n) => n + 1);
setFeedbackOpen(true); setFeedbackOpen(true);
}} }}

61
components/ui/sonner.jsx Normal file
View File

@@ -0,0 +1,61 @@
"use client"
import {
CircleCheckIcon,
InfoIcon,
Loader2Icon,
OctagonXIcon,
TriangleAlertIcon,
} from "lucide-react"
import { useTheme } from "next-themes"
import { Toaster as Sonner } from "sonner";
const Toaster = ({ ...props }) => {
const { theme = "system" } = useTheme();
return (
<Sonner
theme={theme}
// 外层容器固定在页面顶部中间
className="toaster pointer-events-none fixed inset-x-0 top-4 z-[70] flex items-start justify-center px-4 sm:top-6"
icons={{
success: <CircleCheckIcon className="h-4 w-4 text-emerald-500" />,
info: <InfoIcon className="h-4 w-4 text-sky-500" />,
warning: <TriangleAlertIcon className="h-4 w-4 text-amber-500" />,
error: <OctagonXIcon className="h-4 w-4 text-destructive" />,
loading: <Loader2Icon className="h-4 w-4 animate-spin text-primary" />,
}}
richColors
// 统一 toast 样式使用 ui-ux-pro-max 建议的明暗主题对比度
toastOptions={{
classNames: {
toast:
// 基础:浅色模式下使用高对比白色卡片,暗色模式使用深色卡片
"pointer-events-auto relative flex w-full max-w-sm items-start gap-3 rounded-xl border border-slate-200 bg-white/90 text-slate-900 px-4 py-3 shadow-lg shadow-black/10 backdrop-blur-md transition-all duration-200 " +
"data-[state=open]:animate-in data-[state=open]:fade-in data-[state=open]:slide-in-from-top sm:data-[state=open]:slide-in-from-bottom " +
"data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=closed]:slide-out-to-right " +
"data-[swipe=move]:translate-x-[var(--sonner-swipe-move-x)] data-[swipe=move]:transition-none " +
"data-[swipe=cancel]:translate-x-0 data-[swipe=cancel]:transition-transform data-[swipe=end]:translate-x-[var(--sonner-swipe-end-x)] " +
"dark:border-slate-800 dark:bg-slate-900/90 dark:text-slate-100",
title: "text-sm font-medium",
description: "mt-1 text-xs text-slate-600 dark:text-slate-400",
closeButton:
"cursor-pointer text-muted-foreground/70 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
actionButton:
"inline-flex h-8 items-center justify-center rounded-full bg-primary px-3 text-xs font-medium text-primary-foreground shadow-sm transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2",
cancelButton:
"inline-flex h-8 items-center justify-center rounded-full border border-border bg-background px-3 text-xs font-medium text-foreground shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
// 状态色:成功/信息/警告只强化边框,错误使用红色背景,满足你“提示为红色”的需求
success: "border-emerald-500/70",
info: "border-sky-500/70",
warning: "border-amber-500/70",
error: "bg-destructive text-destructive-foreground border-destructive/80",
loading: "border-primary/60",
},
}}
{...props}
/>
);
};
export { Toaster }

22
package-lock.json generated
View File

@@ -26,10 +26,12 @@
"lodash": "^4.17.23", "lodash": "^4.17.23",
"lucide-react": "^0.577.0", "lucide-react": "^0.577.0",
"next": "^16.1.5", "next": "^16.1.5",
"next-themes": "^0.4.6",
"radix-ui": "^1.4.3", "radix-ui": "^1.4.3",
"react": "18.3.1", "react": "18.3.1",
"react-chartjs-2": "^5.3.1", "react-chartjs-2": "^5.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0", "tailwind-merge": "^3.5.0",
"tesseract.js": "^5.1.1", "tesseract.js": "^5.1.1",
"uuid": "^13.0.0", "uuid": "^13.0.0",
@@ -9903,6 +9905,16 @@
} }
} }
}, },
"node_modules/next-themes": {
"version": "0.4.6",
"resolved": "https://registry.npmmirror.com/next-themes/-/next-themes-0.4.6.tgz",
"integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/next/node_modules/postcss": { "node_modules/next/node_modules/postcss": {
"version": "8.4.31", "version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -11669,6 +11681,16 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "url": "https://github.com/chalk/ansi-styles?sponsor=1"
} }
}, },
"node_modules/sonner": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/sonner/-/sonner-2.0.7.tgz",
"integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
"license": "MIT",
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",

View File

@@ -29,10 +29,12 @@
"lodash": "^4.17.23", "lodash": "^4.17.23",
"lucide-react": "^0.577.0", "lucide-react": "^0.577.0",
"next": "^16.1.5", "next": "^16.1.5",
"next-themes": "^0.4.6",
"radix-ui": "^1.4.3", "radix-ui": "^1.4.3",
"react": "18.3.1", "react": "18.3.1",
"react-chartjs-2": "^5.3.1", "react-chartjs-2": "^5.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0", "tailwind-merge": "^3.5.0",
"tesseract.js": "^5.1.1", "tesseract.js": "^5.1.1",
"uuid": "^13.0.0", "uuid": "^13.0.0",