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:
2026-04-16 17:40:27 +07:00
parent 5053ccdba2
commit b26c8199a5
562 changed files with 59030 additions and 37600 deletions

View 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 })
}
)
);

View 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 })
}
)
);

View 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 {}
}
}));