Update skills: add website-creator, mql-developer, ecommerce-astro
Changes: - Add FAL_KEY and GEMINI_API_KEY to .env.example - Update picture-it to use ~/.config/opencode/.env (unified creds) - Remove shodh-memory skill (no longer used) - Remove alphaear-* skills (deprecated) - Remove thai-frontend-dev skill (replaced by website-creator) - Remove theme-factory skill - Add mql-developer skill (MQL5 trading) - Add ecommerce-astro skill (Astro e-commerce) - Add website-creator skill (Next.js + Payload CMS) - Update install script for new skills
This commit is contained in:
113
skills/ecommerce-astro/scripts/templates/src/stores/auth.ts
Normal file
113
skills/ecommerce-astro/scripts/templates/src/stores/auth.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name?: string;
|
||||
role: 'customer' | 'vendor' | 'admin';
|
||||
avatar_url?: string;
|
||||
}
|
||||
|
||||
interface AuthStore {
|
||||
user: User | null;
|
||||
token: string | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
|
||||
login: (email: string, password: string) => Promise<boolean>;
|
||||
register: (data: { email: string; password: string; name: string }) => Promise<boolean>;
|
||||
logout: () => Promise<void>;
|
||||
fetchUser: () => Promise<void>;
|
||||
clearError: () => void;
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
user: null,
|
||||
token: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
|
||||
login: async (email, password) => {
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
const res = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json();
|
||||
set({ error: data.error || 'Login failed' });
|
||||
return false;
|
||||
}
|
||||
|
||||
const { user, token } = await res.json();
|
||||
set({ user, token, isLoading: false });
|
||||
return true;
|
||||
} catch (error) {
|
||||
set({ error: 'Network error', isLoading: false });
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
register: async (data) => {
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
const res = await fetch('/api/auth/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const error = await res.json();
|
||||
set({ error: error.error || 'Registration failed' });
|
||||
return false;
|
||||
}
|
||||
|
||||
const { user, token } = await res.json();
|
||||
set({ user, token, isLoading: false });
|
||||
return true;
|
||||
} catch (error) {
|
||||
set({ error: 'Network error', isLoading: false });
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
logout: async () => {
|
||||
await fetch('/api/auth/logout', { method: 'POST' });
|
||||
set({ user: null, token: null });
|
||||
},
|
||||
|
||||
fetchUser: async () => {
|
||||
const token = get().token;
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/auth/me', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const { user } = await res.json();
|
||||
set({ user });
|
||||
} else {
|
||||
set({ user: null, token: null });
|
||||
}
|
||||
} catch {
|
||||
set({ user: null, token: null });
|
||||
}
|
||||
},
|
||||
|
||||
clearError: () => set({ error: null })
|
||||
}),
|
||||
{
|
||||
name: 'auth-storage',
|
||||
partialize: (state) => ({ user: state.user, token: state.token })
|
||||
}
|
||||
)
|
||||
);
|
||||
88
skills/ecommerce-astro/scripts/templates/src/stores/cart.ts
Normal file
88
skills/ecommerce-astro/scripts/templates/src/stores/cart.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export interface CartItem {
|
||||
id: string;
|
||||
productId: string;
|
||||
name: string;
|
||||
price: number;
|
||||
image: string;
|
||||
quantity: number;
|
||||
variant?: { id: string; name: string };
|
||||
}
|
||||
|
||||
interface CartStore {
|
||||
items: CartItem[];
|
||||
isOpen: boolean;
|
||||
addItem: (item: Omit<CartItem, 'id'>) => void;
|
||||
removeItem: (id: string) => void;
|
||||
updateQuantity: (id: string, quantity: number) => void;
|
||||
clearCart: () => void;
|
||||
toggleCart: () => void;
|
||||
setCartOpen: (open: boolean) => void;
|
||||
getTotal: () => number;
|
||||
getCount: () => number;
|
||||
}
|
||||
|
||||
export const useCartStore = create<CartStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
items: [],
|
||||
isOpen: false,
|
||||
|
||||
addItem: (item) => {
|
||||
const existing = get().items.find(
|
||||
i => i.productId === item.productId && i.variant?.id === item.variant?.id
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
set(state => ({
|
||||
items: state.items.map(i =>
|
||||
i.id === existing.id
|
||||
? { ...i, quantity: i.quantity + item.quantity }
|
||||
: i
|
||||
)
|
||||
}));
|
||||
} else {
|
||||
set(state => ({
|
||||
items: [...state.items, { ...item, id: `${item.productId}-${item.variant?.id || 'default'}-${Date.now()}` }]
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
removeItem: (id) => {
|
||||
set(state => ({ items: state.items.filter(i => i.id !== id) }));
|
||||
},
|
||||
|
||||
updateQuantity: (id, quantity) => {
|
||||
if (quantity <= 0) {
|
||||
get().removeItem(id);
|
||||
return;
|
||||
}
|
||||
set(state => ({
|
||||
items: state.items.map(i =>
|
||||
i.id === id ? { ...i, quantity } : i
|
||||
)
|
||||
}));
|
||||
},
|
||||
|
||||
clearCart: () => set({ items: [] }),
|
||||
|
||||
toggleCart: () => set(state => ({ isOpen: !state.isOpen })),
|
||||
|
||||
setCartOpen: (open) => set({ isOpen: open }),
|
||||
|
||||
getTotal: () => {
|
||||
return get().items.reduce((sum, item) => sum + item.price * item.quantity, 0);
|
||||
},
|
||||
|
||||
getCount: () => {
|
||||
return get().items.reduce((sum, item) => sum + item.quantity, 0);
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: 'cart-storage',
|
||||
partialize: (state) => ({ items: state.items })
|
||||
}
|
||||
)
|
||||
);
|
||||
138
skills/ecommerce-astro/scripts/templates/src/stores/vendor.ts
Normal file
138
skills/ecommerce-astro/scripts/templates/src/stores/vendor.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface VendorStats {
|
||||
products: number;
|
||||
orders: number;
|
||||
totalSales: number;
|
||||
pendingOrders: number;
|
||||
}
|
||||
|
||||
interface VendorStore {
|
||||
store: any | null;
|
||||
products: any[];
|
||||
orders: any[];
|
||||
stats: VendorStats | null;
|
||||
isLoading: boolean;
|
||||
|
||||
fetchDashboard: () => Promise<void>;
|
||||
fetchProducts: () => Promise<void>;
|
||||
fetchOrders: () => Promise<void>;
|
||||
updateProduct: (id: string, data: any) => Promise<void>;
|
||||
createProduct: (data: any) => Promise<boolean>;
|
||||
deleteProduct: (id: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useVendorStore = create<VendorStore>((set, get) => ({
|
||||
store: null,
|
||||
products: [],
|
||||
orders: [],
|
||||
stats: null,
|
||||
isLoading: false,
|
||||
|
||||
fetchDashboard: async () => {
|
||||
set({ isLoading: true });
|
||||
try {
|
||||
const token = localStorage.getItem('auth-storage');
|
||||
const tokenValue = token ? JSON.parse(token).state.token : null;
|
||||
|
||||
const res = await fetch('/api/vendors/dashboard', {
|
||||
headers: { 'Authorization': `Bearer ${tokenValue}` }
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
set({ store: data.vendor, stats: data.stats, isLoading: false });
|
||||
}
|
||||
} catch {
|
||||
set({ isLoading: false });
|
||||
}
|
||||
},
|
||||
|
||||
fetchProducts: async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('auth-storage');
|
||||
const tokenValue = token ? JSON.parse(token).state.token : null;
|
||||
|
||||
const res = await fetch('/api/vendors/products', {
|
||||
headers: { 'Authorization': `Bearer ${tokenValue}` }
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const { products } = await res.json();
|
||||
set({ products });
|
||||
}
|
||||
} catch {}
|
||||
},
|
||||
|
||||
fetchOrders: async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('auth-storage');
|
||||
const tokenValue = token ? JSON.parse(token).state.token : null;
|
||||
|
||||
const res = await fetch('/api/vendors/orders', {
|
||||
headers: { 'Authorization': `Bearer ${tokenValue}` }
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const { orders } = await res.json();
|
||||
set({ orders });
|
||||
}
|
||||
} catch {}
|
||||
},
|
||||
|
||||
createProduct: async (data) => {
|
||||
try {
|
||||
const token = localStorage.getItem('auth-storage');
|
||||
const tokenValue = token ? JSON.parse(token).state.token : null;
|
||||
|
||||
const res = await fetch('/api/products', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${tokenValue}`
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
return res.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
updateProduct: async (id, data) => {
|
||||
try {
|
||||
const token = localStorage.getItem('auth-storage');
|
||||
const tokenValue = token ? JSON.parse(token).state.token : null;
|
||||
|
||||
const res = await fetch(`/api/products/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${tokenValue}`
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
await get().fetchProducts();
|
||||
}
|
||||
} catch {}
|
||||
},
|
||||
|
||||
deleteProduct: async (id) => {
|
||||
try {
|
||||
const token = localStorage.getItem('auth-storage');
|
||||
const tokenValue = token ? JSON.parse(token).state.token : null;
|
||||
|
||||
const res = await fetch(`/api/products/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': `Bearer ${tokenValue}` }
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
await get().fetchProducts();
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}));
|
||||
Reference in New Issue
Block a user