diff --git a/app/components/auth/SubscriptionDialog.tsx b/app/components/auth/SubscriptionDialog.tsx
index 510cf0e..3dbe53b 100644
--- a/app/components/auth/SubscriptionDialog.tsx
+++ b/app/components/auth/SubscriptionDialog.tsx
@@ -4,6 +4,8 @@ import { useAuth } from '~/hooks/useAuth';
import { toast } from 'react-toastify';
import { PaymentModal } from './PaymentModal';
import type { SubscriptionPlan } from '~/types/subscription';
+import pkg from 'lodash';
+const {toString} = pkg;
interface SubscriptionDialogProps {
isOpen: boolean;
@@ -42,16 +44,19 @@ export function SubscriptionDialog({ isOpen, onClose }: SubscriptionDialogProps)
const [subscriptionPlans, setSubscriptionPlans] = useState([]);
const [userSubscription, setUserSubscription] = useState(null);
const [isLoading, setIsLoading] = useState(true);
- const { user } = useAuth();
+ const { user, token, isAuthenticated, login } = useAuth();
const [paymentData, setPaymentData] = useState(null);
useEffect(() => {
if (isOpen) {
- fetchSubscriptionData();
+ fetchSubscriptionPlans();
+ if (isAuthenticated && token) {
+ fetchUserSubscription();
+ }
}
- }, [isOpen]);
+ }, [isOpen, isAuthenticated, token]);
- const fetchSubscriptionData = async () => {
+ const fetchSubscriptionPlans = async () => {
setIsLoading(true);
try {
const response = await fetch('/api/subscription-plans');
@@ -60,23 +65,50 @@ export function SubscriptionDialog({ isOpen, onClose }: SubscriptionDialogProps)
}
const data = await response.json() as SubscriptionPlan[];
setSubscriptionPlans(data);
-
- const userSubResponse = await fetch('/api/user-subscription');
- const userSub = await userSubResponse.json() as UserSubscription;
- setUserSubscription(userSub);
} catch (error) {
- console.error('获取订阅数据时出错:', error);
- toast.error('获取订阅信息失败,请稍后再试');
+ console.error('获取订阅计划时出错:', error);
+ toast.error('获取订阅计划失败,请稍后再试');
} finally {
setIsLoading(false);
}
};
+ const fetchUserSubscription = async () => {
+ if (!token) return;
+ try {
+ const headers = {
+ 'Authorization': `Bearer ${token}`,
+ 'Content-Type': 'application/json'
+ };
+ const userSubResponse = await fetch('/api/user-subscription', { headers });
+ if (!userSubResponse.ok) {
+ throw new Error('获取用户订阅信息失败');
+ }
+ const userSub = await userSubResponse.json() as UserSubscription;
+ setUserSubscription(userSub);
+ } catch (error) {
+ console.error('获取用户订阅信息时出错:', error);
+ toast.error('获取用户订阅信息失败,请稍后再试');
+ }
+ };
+
const handlePurchase = async (planId: string) => {
+ if (!isAuthenticated) {
+ // 如果用户未登录,提示用户登录
+ toast.info('请先登录以继续购买');
+ // 这里可以触发登录流程,例如打开登录对话框
+ // openLoginDialog();
+ return;
+ }
+ if (!token) {
+ toast.error('登录状态异常,请重新登录');
+ return;
+ }
try {
const response = await fetch('/api/purchase-subscription', {
method: 'POST',
headers: {
+ 'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
@@ -97,24 +129,11 @@ export function SubscriptionDialog({ isOpen, onClose }: SubscriptionDialogProps)
};
const handlePaymentSuccess = useCallback(() => {
- fetchSubscriptionData(); // 重新获取订阅信息
+ fetchUserSubscription(); // 重新获取订阅信息
toast.success('订阅成功!');
- }, [fetchSubscriptionData]);
+ }, [fetchUserSubscription]);
- // 类型守卫函数
- function isSubscriptionPlan(plan: any): plan is SubscriptionPlan {
- return (
- typeof plan === 'object' &&
- typeof plan._id === 'string' &&
- typeof plan.name === 'string' &&
- typeof plan.tokens === 'number' &&
- typeof plan.price === 'number' &&
- typeof plan.description === 'string' &&
- (plan.save_percentage === null || typeof plan.save_percentage === 'number')
- );
- }
-
- if (!user || isLoading) return null;
+ if (isLoading) return null;
return (
<>
@@ -129,11 +148,11 @@ export function SubscriptionDialog({ isOpen, onClose }: SubscriptionDialogProps)
- {userSubscription && (
+ {isAuthenticated && userSubscription && (
-
{userSubscription.tokensLeft.toLocaleString()}
+
{toString(userSubscription.tokensLeft)}
代币剩余。
{userSubscription.plan.tokens.toLocaleString()}代币将在{new Date(userSubscription.nextReloadDate).toLocaleDateString()}后添加。
@@ -176,7 +195,7 @@ export function SubscriptionDialog({ isOpen, onClose }: SubscriptionDialogProps)
{subscriptionPlans.map((plan) => (
-
+
{plan.name}
{(plan.tokens / 1000000).toFixed(0)}M 代币
@@ -191,12 +210,12 @@ export function SubscriptionDialog({ isOpen, onClose }: SubscriptionDialogProps)
))}
diff --git a/app/hooks/useAuth.ts b/app/hooks/useAuth.ts
index 6544ff6..63ba6a5 100644
--- a/app/hooks/useAuth.ts
+++ b/app/hooks/useAuth.ts
@@ -12,18 +12,21 @@ export function useAuth() {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [user, setUser] = useState
(null);
+ const [token, setToken] = useState(null);
const navigate = useNavigate();
useEffect(() => {
const checkAuth = () => {
- const token = localStorage.getItem('token');
+ const storedToken = localStorage.getItem('token');
const storedUser = localStorage.getItem('user');
- if (token && storedUser) {
+ if (storedToken && storedUser) {
setIsAuthenticated(true);
setUser(JSON.parse(storedUser));
+ setToken(storedToken);
} else {
setIsAuthenticated(false);
setUser(null);
+ setToken(null);
}
setIsLoading(false);
};
@@ -36,11 +39,12 @@ export function useAuth() {
};
}, []);
- const login = (token: string, userData: User) => {
- localStorage.setItem('token', token);
+ const login = (newToken: string, userData: User) => {
+ localStorage.setItem('token', newToken);
localStorage.setItem('user', JSON.stringify(userData));
setIsAuthenticated(true);
setUser(userData);
+ setToken(newToken);
};
const logout = () => {
@@ -48,8 +52,9 @@ export function useAuth() {
localStorage.removeItem('user');
setIsAuthenticated(false);
setUser(null);
+ setToken(null);
navigate('/');
};
- return { isAuthenticated, isLoading, user, login, logout };
+ return { isAuthenticated, isLoading, user, token, login, logout };
}
diff --git a/app/routes/api.subscription-plans.ts b/app/routes/api.subscription-plans.ts
index 96730e8..59b53da 100644
--- a/app/routes/api.subscription-plans.ts
+++ b/app/routes/api.subscription-plans.ts
@@ -1,5 +1,5 @@
import { json } from '@remix-run/cloudflare';
-import { db } from '~/lib/db.server';
+import { db } from '~/utils/db.server';
export async function loader() {
try {
diff --git a/app/routes/api.user-subscription.ts b/app/routes/api.user-subscription.ts
index a4ccde8..8910f5d 100644
--- a/app/routes/api.user-subscription.ts
+++ b/app/routes/api.user-subscription.ts
@@ -1,10 +1,16 @@
import { json } from '@remix-run/cloudflare';
-import { db } from '~/lib/db.server';
-import { requireUserId } from '~/lib/session.server';
+import { db } from '~/utils/db.server';
+import { requireAuth } from '~/middleware/auth.server';
-export async function loader({ request }) {
- const userId = await requireUserId(request);
- try {
+export async function loader({ request }: { request: Request }) {
+ let userId;
+ try {
+ userId = await requireAuth(request);
+ } catch (error) {
+ return error as Response;
+ }
+
+ try {
const userSubscription = await db.select(
'subscription_plans.*',
'user_transactions.tokens as tokensLeft',
diff --git a/app/utils/SDPay.server.ts b/app/utils/SDPay.server.ts
index 342ed03..16317e0 100644
--- a/app/utils/SDPay.server.ts
+++ b/app/utils/SDPay.server.ts
@@ -1,6 +1,8 @@
import CryptoJS from "crypto-js";
-import { toNumber } from "lodash";
+import pkg from 'lodash';
+
import { env } from "node:process";
+const {toNumber} = pkg;
export interface SDNotifyBody {
no: string;