feat:未配置 supabase 能正常启动项目
This commit is contained in:
@@ -1,11 +1,47 @@
|
|||||||
import { createClient } from '@supabase/supabase-js';
|
import { createClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
// Supabase 配置
|
|
||||||
// 注意:此处使用 publishable key,可安全在客户端使用
|
|
||||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || '';
|
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || '';
|
||||||
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '';
|
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: {
|
auth: {
|
||||||
// 启用自动刷新 token
|
// 启用自动刷新 token
|
||||||
autoRefreshToken: true,
|
autoRefreshToken: true,
|
||||||
@@ -14,4 +50,4 @@ export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
|
|||||||
// 检测 URL 中的 session(用于邮箱验证回调)
|
// 检测 URL 中的 session(用于邮箱验证回调)
|
||||||
detectSessionInUrl: true
|
detectSessionInUrl: true
|
||||||
}
|
}
|
||||||
});
|
}) : createNoopSupabase();
|
||||||
|
|||||||
41
app/page.jsx
41
app/page.jsx
@@ -11,7 +11,7 @@ import Announcement from "./components/Announcement";
|
|||||||
import { DatePicker, DonateTabs, NumericInput, Stat } from "./components/Common";
|
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 { 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 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 { fetchFundData, fetchLatestRelease, fetchShanghaiIndexDate, fetchSmartFundNetValue, searchFunds, submitFeedback } from './api/fund';
|
||||||
import packageJson from '../package.json';
|
import packageJson from '../package.json';
|
||||||
|
|
||||||
@@ -2280,6 +2280,15 @@ export default function HomePage() {
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenLogin = () => {
|
||||||
|
setUserMenuOpen(false);
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
showToast('未配置 Supabase,无法登录', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoginModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
const [updateModalOpen, setUpdateModalOpen] = useState(false);
|
const [updateModalOpen, setUpdateModalOpen] = useState(false);
|
||||||
const [cloudConfigModal, setCloudConfigModal] = useState({ open: false, userId: null });
|
const [cloudConfigModal, setCloudConfigModal] = useState({ open: false, userId: null });
|
||||||
const syncDebounceRef = useRef(null);
|
const syncDebounceRef = useRef(null);
|
||||||
@@ -2514,6 +2523,11 @@ export default function HomePage() {
|
|||||||
|
|
||||||
// 初始化认证状态监听
|
// 初始化认证状态监听
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
setUser(null);
|
||||||
|
setUserMenuOpen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const clearAuthState = () => {
|
const clearAuthState = () => {
|
||||||
setUser(null);
|
setUser(null);
|
||||||
setUserMenuOpen(false);
|
setUserMenuOpen(false);
|
||||||
@@ -2580,7 +2594,7 @@ export default function HomePage() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user?.id) return;
|
if (!isSupabaseConfigured || !user?.id) return;
|
||||||
const channel = supabase
|
const channel = supabase
|
||||||
.channel(`user-configs-${user.id}`)
|
.channel(`user-configs-${user.id}`)
|
||||||
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'user_configs', filter: `user_id=eq.${user.id}` }, async (payload) => {
|
.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();
|
e.preventDefault();
|
||||||
setLoginError('');
|
setLoginError('');
|
||||||
setLoginSuccess('');
|
setLoginSuccess('');
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
showToast('未配置 Supabase,无法登录', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
if (!loginEmail.trim()) {
|
if (!loginEmail.trim()) {
|
||||||
@@ -2647,6 +2665,10 @@ export default function HomePage() {
|
|||||||
setLoginError('请输入邮箱中的验证码');
|
setLoginError('请输入邮箱中的验证码');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
showToast('未配置 Supabase,无法登录', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
setLoginLoading(true);
|
setLoginLoading(true);
|
||||||
const { data, error } = await supabase.auth.verifyOtp({
|
const { data, error } = await supabase.auth.verifyOtp({
|
||||||
@@ -2672,6 +2694,16 @@ export default function HomePage() {
|
|||||||
// 登出
|
// 登出
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
isLoggingOutRef.current = true;
|
isLoggingOutRef.current = true;
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
setLoginModalOpen(false);
|
||||||
|
setLoginError('');
|
||||||
|
setLoginSuccess('');
|
||||||
|
setLoginEmail('');
|
||||||
|
setLoginOtp('');
|
||||||
|
setUserMenuOpen(false);
|
||||||
|
setUser(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const { data: { session } } = await supabase.auth.getSession();
|
const { data: { session } } = await supabase.auth.getSession();
|
||||||
if (session) {
|
if (session) {
|
||||||
@@ -3599,10 +3631,7 @@ export default function HomePage() {
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="user-menu-item"
|
className="user-menu-item"
|
||||||
onClick={() => {
|
onClick={handleOpenLogin}
|
||||||
setUserMenuOpen(false);
|
|
||||||
setLoginModalOpen(true);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<LoginIcon width="16" height="16" />
|
<LoginIcon width="16" height="16" />
|
||||||
<span>登录</span>
|
<span>登录</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user