ai / src /store.ts
Lianjx's picture
Upload 75 files
8fb4cca verified
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { UserAccount, LeadData, AdminConfig, WeddingStyle, GeneratedResult, CompositionMode, Resolution, SubjectType, GenerationStatus, Language, PointHistory, FeedbackItem, GenerationLog } from './types';
import { TRANSLATIONS } from './constants/translations';
// --- Default Configuration ---
const DEFAULT_CONFIG: AdminConfig = {
promoText: "🎉 Spring Wedding Expo: Book online today and get a Free Makeup Trial + ¥500 Voucher!",
promoEnds: new Date(Date.now() + 86400000).toISOString(),
contactPhone: '0592-8888888',
showBanner: true,
footerAddress: "No. 188, Huandao Road, Siming District, Xiamen",
logoUrl: "", // Default empty
shareTitle: "Help me choose a wedding style!",
shareDesc: "I found this amazing AI fitting room at Romantic Life.",
shareImage: "",
redPacketMax: 2000,
slashDifficulty: 0.98,
crmApiUrl: '',
geminiApiKey: '',
geminiApiUrl: '', // Default empty
pointsShare: 10,
pointsInvite: 50,
pointsBook: 100,
pointsVipCost: 100,
// Updated to user's domain
qrCodeUrl: "https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=https://xmlove520.dpdns.org"
};
// --- USER STORE ---
interface UserState {
currentUser: UserAccount | undefined;
allUsers: UserAccount[];
leads: LeadData[];
feedback: FeedbackItem[];
guestFavorites: string[]; // NEW: Favorites for non-logged in users
logs: GenerationLog[]; // NEW: Analytics Logs
// Actions
login: (phone: string, password?: string) => { success: boolean; msg: string };
register: (phone: string, name: string, pass: string) => { success: boolean; msg: string };
logout: () => void;
updateUser: (id: string, updates: Partial<UserAccount>) => void;
resetPassword: (phone: string, newPass: string) => boolean;
addPoints: (amount: number, reason: string) => void;
toggleFavorite: (styleId: string) => void;
// Admin Actions
addLead: (lead: LeadData) => void;
updateLeadStatus: (id: string, status: LeadData['status']) => void;
deleteLead: (id: string) => void;
setLeads: (leads: LeadData[]) => void;
// Feedback Actions
addFeedback: (item: FeedbackItem) => void;
// Analytics Actions
addLog: (log: GenerationLog) => void;
}
export const useUserStore = create<UserState>()(
persist(
(set, get) => ({
currentUser: undefined,
allUsers: [],
leads: [],
feedback: [],
guestFavorites: [],
logs: [],
login: (phone, password) => {
const { allUsers } = get();
const user = allUsers.find(u => u.phone === phone);
if (user) {
if (user.password && password && user.password !== password) {
return { success: false, msg: "Incorrect password" };
}
set({ currentUser: user });
return { success: true, msg: `Welcome ${user.name}` };
}
return { success: false, msg: "User not found" };
},
register: (phone, name, pass) => {
const { allUsers } = get();
if (allUsers.find(u => u.phone === phone)) {
return { success: false, msg: "Phone already registered" };
}
const newUser: UserAccount = {
id: 'user_' + Date.now(),
name,
phone,
password: pass,
points: 100,
isVip: false,
joinDate: Date.now(),
history: [],
role: 'user',
redPacketBalance: 1980,
slashProgress: {},
favorites: []
};
set({ allUsers: [...allUsers, newUser], currentUser: newUser });
return { success: true, msg: "Registration successful" };
},
logout: () => set({ currentUser: undefined }),
updateUser: (id, updates) => {
const { allUsers, currentUser } = get();
const newAllUsers = allUsers.map(u => u.id === id ? { ...u, ...updates } : u);
let newCurrentUser = currentUser;
if (currentUser && currentUser.id === id) {
newCurrentUser = { ...currentUser, ...updates };
}
set({ allUsers: newAllUsers, currentUser: newCurrentUser });
},
resetPassword: (phone, newPass) => {
const { allUsers } = get();
const index = allUsers.findIndex(u => u.phone === phone);
if (index !== -1) {
const updatedUsers = [...allUsers];
updatedUsers[index] = { ...updatedUsers[index], password: newPass };
set({ allUsers: updatedUsers });
return true;
}
return false;
},
addPoints: (amount, reason) => {
const { currentUser, updateUser } = get();
if (!currentUser) return;
const newHistory: PointHistory = {
id: Date.now().toString(),
action: 'share',
points: amount,
timestamp: Date.now(),
desc: reason
};
updateUser(currentUser.id, {
points: currentUser.points + amount,
history: [...currentUser.history, newHistory]
});
},
toggleFavorite: (styleId) => {
const { currentUser, updateUser, guestFavorites } = get();
if (currentUser) {
// Update User Account
const currentFavs = currentUser.favorites || [];
const newFavs = currentFavs.includes(styleId)
? currentFavs.filter(id => id !== styleId)
: [...currentFavs, styleId];
updateUser(currentUser.id, { favorites: newFavs });
} else {
// Update Guest Favorites
const newFavs = guestFavorites.includes(styleId)
? guestFavorites.filter(id => id !== styleId)
: [...guestFavorites, styleId];
set({ guestFavorites: newFavs });
}
},
// Lead Actions
addLead: (lead) => set(state => ({ leads: [lead, ...state.leads] })),
updateLeadStatus: (id, status) => set(state => ({ leads: state.leads.map(l => l.id === id ? { ...l, status } : l) })),
deleteLead: (id) => set(state => ({ leads: state.leads.filter(l => l.id !== id) })),
setLeads: (leads) => set({ leads }),
// Feedback Actions
addFeedback: (item) => set(state => ({ feedback: [item, ...state.feedback] })),
// Analytics
addLog: (log) => set(state => ({ logs: [log, ...state.logs].slice(0, 2000) })), // Keep last 2000 logs
}),
{
name: 'rl_user_storage',
partialize: (state) => ({
allUsers: state.allUsers,
leads: state.leads,
currentUser: state.currentUser,
feedback: state.feedback,
guestFavorites: state.guestFavorites,
logs: state.logs
}),
}
)
);
// --- UI STORE ---
interface UIState {
language: Language;
adminConfig: AdminConfig;
// Modals Visibility
modals: {
auth: boolean;
userCenter: boolean;
admin: boolean;
about: boolean;
feedback: boolean;
share: boolean;
consult: boolean;
analysis: boolean;
redPacket: boolean;
slash: boolean;
};
// Actions
setLanguage: (lang: Language) => void;
setAdminConfig: (config: AdminConfig) => void;
toggleModal: (modal: keyof UIState['modals'], isOpen: boolean) => void;
closeAllModals: () => void;
// Toast
toastMsg: string | null;
showToast: (msg: string) => void;
}
export const useUIStore = create<UIState>()(
persist(
(set) => ({
language: 'zh',
adminConfig: DEFAULT_CONFIG,
modals: {
auth: false,
userCenter: false,
admin: false,
about: false,
feedback: false,
share: false,
consult: false,
analysis: false,
redPacket: false,
slash: false,
},
toastMsg: null,
setLanguage: (lang) => set({ language: lang }),
setAdminConfig: (config) => set({ adminConfig: config }),
toggleModal: (modal, isOpen) => set(state => ({ modals: { ...state.modals, [modal]: isOpen } })),
closeAllModals: () => set(state => {
const closed = Object.keys(state.modals).reduce((acc, key) => ({...acc, [key]: false}), {} as any);
return { modals: closed };
}),
showToast: (msg) => {
set({ toastMsg: msg });
setTimeout(() => set({ toastMsg: null }), 3000);
}
}),
{
name: 'rl_ui_storage',
partialize: (state) => ({ language: state.language, adminConfig: state.adminConfig }), // Don't persist modal state
}
)
);
// --- GENERATION STORE ---
interface GenerationState {
uploadedImages: string[];
selectedStyle: WeddingStyle | null;
customStyleImage: string | null;
// Config
filter: string;
blurAmount: number;
compositionMode: CompositionMode;
resolution: Resolution;
subjectType: SubjectType;
customPrompt: string;
// Results
results: Record<string, GeneratedResult>;
status: GenerationStatus;
progress: { current: number, total: number, statusMsg?: string } | null;
errorMsg: string | null;
// AI Analysis
isScanning: boolean;
scanStep: number;
recommendedStyleIds: string[];
analysisResult: { faceShape: string; skinTone: string; bestVibe: string; };
// Actions
setUploadedImages: (images: string[]) => void;
setSelectedStyle: (style: WeddingStyle | null) => void;
setCustomStyleImage: (img: string | null) => void;
setGenerationConfig: (config: Partial<{ filter: string, blurAmount: number, compositionMode: CompositionMode, resolution: Resolution, subjectType: SubjectType, customPrompt: string }>) => void;
setStatus: (status: GenerationStatus) => void;
setProgress: (progress: GenerationState['progress']) => void;
setErrorMsg: (msg: string | null) => void;
addResult: (result: GeneratedResult) => void;
setResults: (results: Record<string, GeneratedResult>) => void;
resetGeneration: () => void;
// Analysis Actions
startScanning: () => void;
setScanStep: (step: number) => void;
setAnalysisData: (recs: string[], result: { faceShape: string; skinTone: string; bestVibe: string; }) => void;
stopScanning: () => void;
}
export const useGenerationStore = create<GenerationState>()((set) => ({
uploadedImages: [],
selectedStyle: null,
customStyleImage: null,
filter: 'none',
blurAmount: 0,
compositionMode: 'classic',
resolution: 'standard',
subjectType: 'female',
customPrompt: '',
results: {},
status: 'idle',
progress: null,
errorMsg: null,
isScanning: false,
scanStep: 0,
recommendedStyleIds: [],
analysisResult: { faceShape: '', skinTone: '', bestVibe: '' },
setUploadedImages: (images) => set({ uploadedImages: images }),
setSelectedStyle: (style) => set({ selectedStyle: style }),
setCustomStyleImage: (img) => set({ customStyleImage: img }),
setGenerationConfig: (config) => set(state => ({ ...state, ...config })),
setStatus: (status) => set({ status }),
setProgress: (progress) => set({ progress }),
setErrorMsg: (errorMessage) => set({ errorMsg: errorMessage }),
addResult: (result) => set(state => ({ results: { ...state.results, [result.styleId]: result } })),
setResults: (results) => set({ results }),
resetGeneration: () => set({
status: 'idle',
results: {},
uploadedImages: [],
selectedStyle: null,
customStyleImage: null,
filter: 'none',
blurAmount: 0,
customPrompt: '',
compositionMode: 'classic',
resolution: 'standard',
progress: null,
recommendedStyleIds: []
}),
startScanning: () => set({ isScanning: true, scanStep: 0 }),
setScanStep: (step) => set({ scanStep: step }),
setAnalysisData: (recs, result) => set({ recommendedStyleIds: recs, analysisResult: result }),
stopScanning: () => set({ isScanning: false })
}));