yslootahrobotics/src/store/usePricingStore.ts
Najjar\NajjarV02 51671b85b8 feat: Integrate Stripe payment processing and enhance admin settings management
- Removed the previous demo payment system and implemented a real integration with Stripe for handling payments.
- Added new API routes for creating payment intents and handling webhooks from Stripe.
- Updated the database schema to include an Order model for storing payment details.
- Enhanced the admin page to manage pricing items, including the ability to upload 3D models.
- Introduced a settings management feature for the admin panel, allowing for dynamic key-value pairs.
- Improved the RobotModel component to support dynamic attire based on uploaded models.
- Added error handling and validation for file uploads and settings management.
2026-04-14 10:14:18 +04:00

133 lines
3.8 KiB
TypeScript

import { createStore } from 'zustand/vanilla';
import { useSyncExternalStore } from 'react';
export interface PricingItem {
id: string;
label: string;
price: number;
modelPath?: string;
}
export interface PricingState {
items: PricingItem[];
isHydrated: boolean;
}
export interface PricingActions {
updatePrice: (itemId: string, newPrice: number) => void;
updateItem: (itemId: string, updates: Partial<Pick<PricingItem, 'label' | 'price' | 'modelPath'>>) => void;
addItem: (item: PricingItem) => void;
removeItem: (itemId: string) => void;
resetPrices: () => void;
hydrate: () => void;
}
export type PricingStore = PricingState & PricingActions;
const DEFAULT_ITEMS: PricingItem[] = [
{ id: 'base', label: 'G1 Robot Base', price: 250000 },
{ id: 'emarati-kandura', label: 'Emarati Kandura', price: 15000 },
{ id: 'industrial-vest', label: 'Industrial Vest', price: 8500 },
{ id: 'business-suit', label: 'Business Suit', price: 12000 },
{ id: 'custom-color', label: 'Custom Color', price: 3500 },
];
const STORAGE_KEY = 'lootah-pricing';
function loadFromStorage(): PricingItem[] | null {
if (typeof window === 'undefined') return null;
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return null;
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) return null;
return parsed;
} catch {
return null;
}
}
function saveToStorage(items: PricingItem[]) {
if (typeof window === 'undefined') return;
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
} catch {
// Storage full or unavailable
}
}
export const pricingStore = createStore<PricingStore>((set, get) => ({
items: DEFAULT_ITEMS,
isHydrated: false,
updatePrice: (itemId: string, newPrice: number) => {
set((state) => {
const updated = state.items.map((item) =>
item.id === itemId ? { ...item, price: newPrice } : item
);
saveToStorage(updated);
return { items: updated };
});
},
updateItem: (itemId: string, updates: Partial<Pick<PricingItem, 'label' | 'price' | 'modelPath'>>) => {
set((state) => {
const updated = state.items.map((item) =>
item.id === itemId ? { ...item, ...updates } : item
);
saveToStorage(updated);
return { items: updated };
});
},
resetPrices: () => {
saveToStorage(DEFAULT_ITEMS);
set({ items: [...DEFAULT_ITEMS] });
},
addItem: (item: PricingItem) => {
set((state) => {
if (state.items.some((i) => i.id === item.id)) return state;
const updated = [...state.items, item];
saveToStorage(updated);
return { items: updated };
});
},
removeItem: (itemId: string) => {
// Prevent removing the base robot price
if (itemId === 'base') return;
set((state) => {
const updated = state.items.filter((i) => i.id !== itemId);
saveToStorage(updated);
return { items: updated };
});
},
hydrate: () => {
const stored = loadFromStorage();
if (stored && stored.length > 0) {
// Use stored items directly (preserves custom labels, prices, added items).
// Re-add any default items that were never stored (fresh install gap).
const storedIds = new Set(stored.map((s) => s.id));
const missing = DEFAULT_ITEMS.filter((d) => !storedIds.has(d.id));
set({ items: [...stored, ...missing], isHydrated: true });
} else {
set({ isHydrated: true });
}
},
}));
export const usePricingStore = <T>(selector: (state: PricingStore) => T): T => {
return useSyncExternalStore(
pricingStore.subscribe,
() => selector(pricingStore.getState()),
() => selector(pricingStore.getState())
);
};
export const getPrice = (itemId: string): number => {
const item = pricingStore.getState().items.find((i) => i.id === itemId);
return item?.price ?? 0;
};