"use client" import { create } from "zustand" import { persist } from "zustand/middleware" import { getFullBookPrice } from "./book-data" export interface User { id: string phone: string nickname: string isAdmin: boolean purchasedSections: string[] hasFullBook: boolean referralCode: string referredBy?: string earnings: number pendingEarnings: number withdrawnEarnings: number referralCount: number createdAt: string } export interface Withdrawal { id: string userId: string amount: number method: "wechat" | "alipay" account: string name: string status: "pending" | "completed" | "rejected" createdAt: string completedAt?: string } export interface Purchase { id: string userId: string userPhone?: string userNickname?: string type: "section" | "fullbook" sectionId?: string sectionTitle?: string amount: number paymentMethod?: string referralCode?: string referrerEarnings?: number status: "pending" | "completed" | "refunded" createdAt: string } export interface LiveQRCodeConfig { id: string name: string urls: string[] // 多个URL随机跳转 imageUrl: string redirectType: "random" | "sequential" | "weighted" weights?: number[] clickCount: number enabled: boolean } export interface QRCodeConfig { id: string name: string url: string imageUrl: string weight: number enabled: boolean } export interface PaymentAccountConfig { wechat: { enabled: boolean qrCode: string account: string websiteAppId?: string websiteAppSecret?: string serviceAppId?: string serviceAppSecret?: string mpVerifyCode?: string merchantId?: string apiKey?: string } alipay: { enabled: boolean qrCode: string account: string partnerId?: string // PID securityKey?: string mobilePayEnabled?: boolean paymentInterface?: string } usdt: { enabled: boolean network: "TRC20" | "ERC20" | "BEP20" address: string exchangeRate: number } paypal: { enabled: boolean email: string exchangeRate: number } } export interface FeishuSyncConfig { enabled: boolean docUrl: string lastSyncAt?: string autoSync: boolean syncInterval: number // 分钟 } export interface Settings { distributorShare: number authorShare: number paymentMethods: PaymentAccountConfig sectionPrice: number baseBookPrice: number pricePerSection: number qrCodes: QRCodeConfig[] liveQRCodes: LiveQRCodeConfig[] feishuSync: FeishuSyncConfig authorInfo: { name: string description: string liveTime: string platform: string } } interface StoreState { user: User | null isLoggedIn: boolean purchases: Purchase[] withdrawals: Withdrawal[] settings: Settings login: (phone: string, code: string) => Promise logout: () => void register: (phone: string, nickname: string, referralCode?: string) => Promise purchaseSection: (sectionId: string, sectionTitle?: string, paymentMethod?: string) => Promise purchaseFullBook: (paymentMethod?: string) => Promise hasPurchased: (sectionId: string) => boolean adminLogin: (username: string, password: string) => boolean updateSettings: (newSettings: Partial) => void getAllUsers: () => User[] getAllPurchases: () => Purchase[] addUser: (user: Partial) => User updateUser: (userId: string, updates: Partial) => void deleteUser: (userId: string) => void addPurchase: (purchase: Omit) => void requestWithdrawal: (amount: number, method: "wechat" | "alipay", account: string, name: string) => void completeWithdrawal: (id: string) => void updateLiveQRCode: (config: Partial) => void getRandomQRCode: () => QRCodeConfig | null getLiveQRCodeUrl: (qrId: string) => string | null exportData: () => string fetchSettings: () => Promise } const defaultSettings: Settings = { distributorShare: 90, authorShare: 10, paymentMethods: { wechat: { enabled: true, qrCode: "", account: "", websiteAppId: "", websiteAppSecret: "", serviceAppId: "", serviceAppSecret: "", mpVerifyCode: "", merchantId: "", apiKey: "", }, alipay: { enabled: true, qrCode: "", account: "", partnerId: "", securityKey: "", mobilePayEnabled: true, paymentInterface: "official", }, usdt: { enabled: true, network: "TRC20", address: "", exchangeRate: 7.2, }, paypal: { enabled: false, email: "", exchangeRate: 7.2, }, }, sectionPrice: 1, baseBookPrice: 9.9, pricePerSection: 1, qrCodes: [ { id: "default", name: "Soul派对群", url: "https://soul.cn/party", imageUrl: "/images/image.png", weight: 1, enabled: true, }, ], liveQRCodes: [ { id: "party-group", name: "派对群活码", urls: ["https://soul.cn/party1", "https://soul.cn/party2", "https://soul.cn/party3"], imageUrl: "/images/image.png", redirectType: "random", clickCount: 0, enabled: true, }, ], feishuSync: { enabled: false, docUrl: "", autoSync: false, syncInterval: 60, }, authorInfo: { name: "卡若", description: "连续创业者,私域运营专家,每天早上6-9点在Soul派对房分享真实商业故事", liveTime: "06:00-09:00", platform: "Soul派对房", }, } export const useStore = create()( persist( (set, get) => ({ user: null, isLoggedIn: false, purchases: [], withdrawals: [], settings: defaultSettings, login: async (phone: string, code: string) => { if (code !== "123456") { return false } const users = JSON.parse(localStorage.getItem("users") || "[]") as User[] const existingUser = users.find((u) => u.phone === phone) if (existingUser) { set({ user: existingUser, isLoggedIn: true }) return true } return false }, logout: () => { set({ user: null, isLoggedIn: false }) }, register: async (phone: string, nickname: string, referralCode?: string) => { const users = JSON.parse(localStorage.getItem("users") || "[]") as User[] if (users.find((u) => u.phone === phone)) { return false } const newUser: User = { id: `user_${Date.now()}`, phone, nickname, isAdmin: false, purchasedSections: [], hasFullBook: false, referralCode: `REF${Date.now().toString(36).toUpperCase()}`, referredBy: referralCode, earnings: 0, pendingEarnings: 0, withdrawnEarnings: 0, referralCount: 0, createdAt: new Date().toISOString(), } if (referralCode) { const referrer = users.find((u) => u.referralCode === referralCode) if (referrer) { referrer.referralCount = (referrer.referralCount || 0) + 1 localStorage.setItem("users", JSON.stringify(users)) } } users.push(newUser) localStorage.setItem("users", JSON.stringify(users)) set({ user: newUser, isLoggedIn: true }) return true }, purchaseSection: async (sectionId: string, sectionTitle?: string, paymentMethod?: string) => { const { user, settings } = get() if (!user) return false const amount = settings.sectionPrice const purchase: Purchase = { id: `purchase_${Date.now()}`, userId: user.id, userPhone: user.phone, userNickname: user.nickname, type: "section", sectionId, sectionTitle, amount, paymentMethod, referralCode: user.referredBy, status: "completed", createdAt: new Date().toISOString(), } const updatedUser = { ...user, purchasedSections: [...user.purchasedSections, sectionId], } const users = JSON.parse(localStorage.getItem("users") || "[]") as User[] const userIndex = users.findIndex((u) => u.id === user.id) if (userIndex !== -1) { users[userIndex] = updatedUser localStorage.setItem("users", JSON.stringify(users)) } if (user.referredBy) { const referrer = users.find((u) => u.referralCode === user.referredBy) if (referrer) { const referrerEarnings = amount * (settings.distributorShare / 100) referrer.earnings += referrerEarnings referrer.pendingEarnings = (referrer.pendingEarnings || 0) + referrerEarnings purchase.referrerEarnings = referrerEarnings localStorage.setItem("users", JSON.stringify(users)) } } const purchases = JSON.parse(localStorage.getItem("all_purchases") || "[]") as Purchase[] purchases.push(purchase) localStorage.setItem("all_purchases", JSON.stringify(purchases)) set({ user: updatedUser, purchases: [...get().purchases, purchase] }) return true }, purchaseFullBook: async (paymentMethod?: string) => { const { user, settings } = get() if (!user) return false const fullBookPrice = getFullBookPrice() const purchase: Purchase = { id: `purchase_${Date.now()}`, userId: user.id, userPhone: user.phone, userNickname: user.nickname, type: "fullbook", amount: fullBookPrice, paymentMethod, referralCode: user.referredBy, status: "completed", createdAt: new Date().toISOString(), } const updatedUser = { ...user, hasFullBook: true } const users = JSON.parse(localStorage.getItem("users") || "[]") as User[] const userIndex = users.findIndex((u) => u.id === user.id) if (userIndex !== -1) { users[userIndex] = updatedUser localStorage.setItem("users", JSON.stringify(users)) } if (user.referredBy) { const referrer = users.find((u) => u.referralCode === user.referredBy) if (referrer) { const referrerEarnings = fullBookPrice * (settings.distributorShare / 100) referrer.earnings += referrerEarnings referrer.pendingEarnings = (referrer.pendingEarnings || 0) + referrerEarnings purchase.referrerEarnings = referrerEarnings localStorage.setItem("users", JSON.stringify(users)) } } const purchases = JSON.parse(localStorage.getItem("all_purchases") || "[]") as Purchase[] purchases.push(purchase) localStorage.setItem("all_purchases", JSON.stringify(purchases)) set({ user: updatedUser, purchases: [...get().purchases, purchase] }) return true }, hasPurchased: (sectionId: string) => { const { user } = get() if (!user) return false if (user.hasFullBook) return true return user.purchasedSections.includes(sectionId) }, adminLogin: (username: string, password: string) => { if (username.toLowerCase() === "admin" && password === "key123456") { const adminUser: User = { id: "admin", phone: "admin", nickname: "管理员", isAdmin: true, purchasedSections: [], hasFullBook: true, referralCode: "ADMIN", earnings: 0, pendingEarnings: 0, withdrawnEarnings: 0, referralCount: 0, createdAt: new Date().toISOString(), } set({ user: adminUser, isLoggedIn: true }) return true } return false }, updateSettings: (newSettings: Partial) => { const { settings } = get() const updatedSettings = { ...settings, ...newSettings } if (newSettings.distributorShare !== undefined) { updatedSettings.authorShare = 100 - newSettings.distributorShare } set({ settings: updatedSettings }) if (typeof window !== "undefined") { localStorage.setItem("app_settings", JSON.stringify(updatedSettings)) } }, getAllUsers: () => { if (typeof window === "undefined") return [] return JSON.parse(localStorage.getItem("users") || "[]") as User[] }, getAllPurchases: () => { if (typeof window === "undefined") return [] return JSON.parse(localStorage.getItem("all_purchases") || "[]") as Purchase[] }, addUser: (userData: Partial) => { if (typeof window === "undefined") return { id: "temp", ...userData } as User const users = JSON.parse(localStorage.getItem("users") || "[]") as User[] const newUser: User = { id: `user_${Date.now()}`, phone: userData.phone || "", nickname: userData.nickname || "新用户", isAdmin: false, purchasedSections: [], hasFullBook: false, referralCode: `REF${Date.now().toString(36).toUpperCase()}`, earnings: 0, pendingEarnings: 0, withdrawnEarnings: 0, referralCount: 0, createdAt: new Date().toISOString(), ...userData, } users.push(newUser) localStorage.setItem("users", JSON.stringify(users)) return newUser }, updateUser: (userId: string, updates: Partial) => { if (typeof window === "undefined") return const users = JSON.parse(localStorage.getItem("users") || "[]") as User[] const index = users.findIndex((u) => u.id === userId) if (index !== -1) { users[index] = { ...users[index], ...updates } localStorage.setItem("users", JSON.stringify(users)) } }, deleteUser: (userId: string) => { if (typeof window === "undefined") return const users = JSON.parse(localStorage.getItem("users") || "[]") as User[] const filtered = users.filter((u) => u.id !== userId) localStorage.setItem("users", JSON.stringify(filtered)) }, addPurchase: (purchaseData) => set((state) => { const newPurchase: Purchase = { id: Math.random().toString(36).substring(2, 9), createdAt: new Date().toISOString(), ...purchaseData, } // 如果是全书购买,更新用户状态 if (state.user && purchaseData.userId === state.user.id) { const updatedUser = { ...state.user } if (purchaseData.type === "fullbook") { updatedUser.hasFullBook = true } else if (purchaseData.sectionId) { updatedUser.purchasedSections = [ ...updatedUser.purchasedSections, purchaseData.sectionId, ] } // 更新 users 数组 const updatedUsers = state.users?.map((u) => u.id === updatedUser.id ? updatedUser : u ) || [] return { purchases: [...state.purchases, newPurchase], user: updatedUser, } } return { purchases: [...state.purchases, newPurchase], } }), requestWithdrawal: (amount, method, account, name) => set((state) => { if (!state.user) return {} if (state.user.earnings < amount) return {} const newWithdrawal: Withdrawal = { id: Math.random().toString(36).substring(2, 9), userId: state.user.id, amount, method, account, name, status: "pending", createdAt: new Date().toISOString() } // 扣除余额,增加冻结/提现中金额 const updatedUser = { ...state.user, earnings: state.user.earnings - amount, pendingEarnings: state.user.pendingEarnings + amount } return { withdrawals: [...(state.withdrawals || []), newWithdrawal], user: updatedUser, } }), completeWithdrawal: (id) => set((state) => { const withdrawals = state.withdrawals || [] const withdrawalIndex = withdrawals.findIndex(w => w.id === id) if (withdrawalIndex === -1) return {} const withdrawal = withdrawals[withdrawalIndex] if (withdrawal.status !== "pending") return {} const updatedWithdrawals = [...withdrawals] updatedWithdrawals[withdrawalIndex] = { ...withdrawal, status: "completed", completedAt: new Date().toISOString() } // 这里我们只是更新状态,资金已经在申请时扣除了 // 实际场景中可能需要确认转账成功 return { withdrawals: updatedWithdrawals, } }), updateLiveQRCode: (config) => set((state) => { const { settings } = state const updatedLiveQRCodes = settings.liveQRCodes.map((qr) => qr.id === config.id ? { ...qr, ...config } : qr ) // 如果不存在且有id,则添加? 暂时只支持更新 return { settings: { ...settings, liveQRCodes: updatedLiveQRCodes }, } }), getRandomQRCode: () => { const { settings } = get() const enabledQRs = settings.qrCodes.filter((qr) => qr.enabled) if (enabledQRs.length === 0) return null const totalWeight = enabledQRs.reduce((sum, qr) => sum + qr.weight, 0) let random = Math.random() * totalWeight for (const qr of enabledQRs) { random -= qr.weight if (random <= 0) return qr } return enabledQRs[0] }, getLiveQRCodeUrl: (qrId: string) => { const { settings } = get() const liveQR = settings.liveQRCodes.find((qr) => qr.id === qrId && qr.enabled) if (!liveQR || liveQR.urls.length === 0) return null // 更新点击次数 liveQR.clickCount++ if (liveQR.redirectType === "random") { const randomIndex = Math.floor(Math.random() * liveQR.urls.length) return liveQR.urls[randomIndex] } else if (liveQR.redirectType === "sequential") { const index = liveQR.clickCount % liveQR.urls.length return liveQR.urls[index] } else if (liveQR.redirectType === "weighted" && liveQR.weights) { const totalWeight = liveQR.weights.reduce((sum, w) => sum + w, 0) let random = Math.random() * totalWeight for (let i = 0; i < liveQR.urls.length; i++) { random -= liveQR.weights[i] || 1 if (random <= 0) return liveQR.urls[i] } } return liveQR.urls[0] }, exportData: () => { const { user, purchases, settings } = get() const data = { user, purchases, settings, exportDate: new Date().toISOString(), } return JSON.stringify(data, null, 2) }, fetchSettings: async () => { try { const res = await fetch("/api/config") if (!res.ok) throw new Error("Failed to fetch config") const data = await res.json() const { settings } = get() // Deep merge payment methods to preserve existing defaults if API is partial const mergedPaymentMethods = { ...settings.paymentMethods, wechat: { ...settings.paymentMethods.wechat, ...data.paymentMethods?.wechat }, alipay: { ...settings.paymentMethods.alipay, ...data.paymentMethods?.alipay }, usdt: { ...settings.paymentMethods.usdt, ...data.paymentMethods?.usdt }, paypal: { ...settings.paymentMethods.paypal, ...data.paymentMethods?.paypal }, } const newSettings: Partial = { paymentMethods: mergedPaymentMethods, authorInfo: { ...settings.authorInfo, ...data.authorInfo }, } set({ settings: { ...settings, ...newSettings } }) } catch (error) { console.error("Failed to sync settings:", error) } }, }), { name: "soul-experiment-storage", }, ), )