From 7ceb43e7a66b93f272e29dc2355c62540a2fa153 Mon Sep 17 00:00:00 2001 From: hzm <934585316@qq.com> Date: Mon, 9 Feb 2026 08:15:18 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E6=9C=AA=E9=85=8D=E7=BD=AE=20supa?= =?UTF-8?q?base=20=E8=83=BD=E6=AD=A3=E5=B8=B8=E5=90=AF=E5=8A=A8=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lib/supabase.js | 44 ++++++++++++++++++++++++++++++++++++++++---- app/page.jsx | 41 +++++++++++++++++++++++++++++++++++------ 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/app/lib/supabase.js b/app/lib/supabase.js index c4400a9..d4173bb 100644 --- a/app/lib/supabase.js +++ b/app/lib/supabase.js @@ -1,11 +1,47 @@ import { createClient } from '@supabase/supabase-js'; -// Supabase 配置 -// 注意:此处使用 publishable key,可安全在客户端使用 const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || ''; const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || ''; +export const isSupabaseConfigured = Boolean(supabaseUrl && supabaseAnonKey); -export const supabase = createClient(supabaseUrl, supabaseAnonKey, { +const createNoopChannel = () => { + const channel = { + on: () => channel, + subscribe: () => channel + }; + return channel; +}; + +const createNoopTable = () => { + return { + select: () => ({ + eq: () => ({ + maybeSingle: async () => ({ data: null, error: { message: 'Supabase not configured' } }) + }) + }), + insert: async () => ({ data: null, error: { message: 'Supabase not configured' } }), + upsert: () => ({ + select: async () => ({ data: null, error: { message: 'Supabase not configured' } }) + }) + }; +}; + +const createNoopSupabase = () => ({ + auth: { + getSession: async () => ({ data: { session: null }, error: null }), + onAuthStateChange: () => ({ + data: { subscription: { unsubscribe: () => { } } } + }), + signInWithOtp: async () => ({ data: null, error: { message: 'Supabase not configured' } }), + verifyOtp: async () => ({ data: null, error: { message: 'Supabase not configured' } }), + signOut: async () => ({ error: null }) + }, + from: () => createNoopTable(), + channel: () => createNoopChannel(), + removeChannel: () => { } +}); + +export const supabase = isSupabaseConfigured ? createClient(supabaseUrl, supabaseAnonKey, { auth: { // 启用自动刷新 token autoRefreshToken: true, @@ -14,4 +50,4 @@ export const supabase = createClient(supabaseUrl, supabaseAnonKey, { // 检测 URL 中的 session(用于邮箱验证回调) detectSessionInUrl: true } -}); +}) : createNoopSupabase(); diff --git a/app/page.jsx b/app/page.jsx index c503600..d5a7bd2 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -11,7 +11,7 @@ import Announcement from "./components/Announcement"; import { DatePicker, DonateTabs, NumericInput, Stat } from "./components/Common"; import { ChevronIcon, CloseIcon, CloudIcon, DragIcon, ExitIcon, GridIcon, ListIcon, LoginIcon, LogoutIcon, MailIcon, PlusIcon, RefreshIcon, SettingsIcon, SortIcon, StarIcon, TrashIcon, UpdateIcon, UserIcon } from "./components/Icons"; import githubImg from "./assets/github.svg"; -import { supabase } from './lib/supabase'; +import { supabase, isSupabaseConfigured } from './lib/supabase'; import { fetchFundData, fetchLatestRelease, fetchShanghaiIndexDate, fetchSmartFundNetValue, searchFunds, submitFeedback } from './api/fund'; import packageJson from '../package.json'; @@ -2280,6 +2280,15 @@ export default function HomePage() { }, 3000); }; + const handleOpenLogin = () => { + setUserMenuOpen(false); + if (!isSupabaseConfigured) { + showToast('未配置 Supabase,无法登录', 'error'); + return; + } + setLoginModalOpen(true); + }; + const [updateModalOpen, setUpdateModalOpen] = useState(false); const [cloudConfigModal, setCloudConfigModal] = useState({ open: false, userId: null }); const syncDebounceRef = useRef(null); @@ -2514,6 +2523,11 @@ export default function HomePage() { // 初始化认证状态监听 useEffect(() => { + if (!isSupabaseConfigured) { + setUser(null); + setUserMenuOpen(false); + return; + } const clearAuthState = () => { setUser(null); setUserMenuOpen(false); @@ -2580,7 +2594,7 @@ export default function HomePage() { }, []); useEffect(() => { - if (!user?.id) return; + if (!isSupabaseConfigured || !user?.id) return; const channel = supabase .channel(`user-configs-${user.id}`) .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'user_configs', filter: `user_id=eq.${user.id}` }, async (payload) => { @@ -2607,6 +2621,10 @@ export default function HomePage() { e.preventDefault(); setLoginError(''); setLoginSuccess(''); + if (!isSupabaseConfigured) { + showToast('未配置 Supabase,无法登录', 'error'); + return; + } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!loginEmail.trim()) { @@ -2647,6 +2665,10 @@ export default function HomePage() { setLoginError('请输入邮箱中的验证码'); return; } + if (!isSupabaseConfigured) { + showToast('未配置 Supabase,无法登录', 'error'); + return; + } try { setLoginLoading(true); const { data, error } = await supabase.auth.verifyOtp({ @@ -2672,6 +2694,16 @@ export default function HomePage() { // 登出 const handleLogout = async () => { isLoggingOutRef.current = true; + if (!isSupabaseConfigured) { + setLoginModalOpen(false); + setLoginError(''); + setLoginSuccess(''); + setLoginEmail(''); + setLoginOtp(''); + setUserMenuOpen(false); + setUser(null); + return; + } try { const { data: { session } } = await supabase.auth.getSession(); if (session) { @@ -3599,10 +3631,7 @@ export default function HomePage() { <>