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,316 @@
'use client'
import { useState, useEffect } from 'react'
interface ConsentState {
analytics: boolean
marketing: boolean
functional: boolean
hasConsented: boolean
timestamp?: string
}
const defaultConsent: ConsentState = {
analytics: false,
marketing: false,
functional: false,
hasConsented: false,
}
const STORAGE_KEY = 'pdpa_consent'
export function CookieBanner() {
const [consent, setConsent] = useState<ConsentState>(defaultConsent)
const [showBanner, setShowBanner] = useState(false)
const [showPreferences, setShowPreferences] = useState(false)
// Load consent from localStorage on mount
useEffect(() => {
const stored = localStorage.getItem(STORAGE_KEY)
if (stored) {
try {
const parsed = JSON.parse(stored)
setConsent(parsed)
setShowBanner(false)
} catch {
setShowBanner(true)
}
} else {
setShowBanner(true)
}
}, [])
// Save consent to localStorage
const saveConsent = async (newConsent: ConsentState) => {
// Save to localStorage
localStorage.setItem(STORAGE_KEY, JSON.stringify(newConsent))
setConsent(newConsent)
setShowBanner(false)
setShowPreferences(false)
// Log to server
try {
await fetch('/api/consent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: newConsent.hasConsented ? 'accept' : 'reject',
purpose: 'all',
...newConsent,
}),
})
} catch (error) {
console.error('Failed to log consent:', error)
}
}
// Accept all cookies
const acceptAll = () => {
saveConsent({
analytics: true,
marketing: true,
functional: true,
hasConsented: true,
timestamp: new Date().toISOString(),
})
}
// Reject all cookies (only functional)
const rejectAll = () => {
saveConsent({
analytics: false,
marketing: false,
functional: false,
hasConsented: true,
timestamp: new Date().toISOString(),
})
}
// Save custom preferences
const savePreferences = () => {
saveConsent({
...consent,
hasConsented: true,
timestamp: new Date().toISOString(),
})
}
// Update individual preference
const updatePreference = (key: keyof Pick<ConsentState, 'analytics' | 'marketing' | 'functional'>, value: boolean) => {
setConsent(prev => ({ ...prev, [key]: value }))
}
// If no banner to show, return null
if (!showBanner) return null
return (
<div
style={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
backgroundColor: '#ffffff',
boxShadow: '0 -4px 20px rgba(0, 0, 0, 0.15)',
padding: '1.5rem',
zIndex: 9999,
borderTop: '1px solid #e5e5e5',
}}
role="dialog"
aria-label="Cookie Consent Banner"
>
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
{!showPreferences ? (
// Main banner
<div>
<h3 style={{ margin: '0 0 0.75rem 0', fontSize: '1.125rem', fontWeight: 600 }}>
🍪 PDPA Cookie Consent
</h3>
<p style={{ margin: '0 0 1rem 0', color: '#555', fontSize: '0.9375rem', lineHeight: 1.5 }}>
We use cookies to enhance your experience. By continuing to visit this site, you agree to our use of cookies.{' '}
<a href="/privacy-policy" style={{ color: '#0066cc' }}>
Learn more
</a>
</p>
<div style={{ display: 'flex', gap: '0.75rem', flexWrap: 'wrap' }}>
<button
onClick={acceptAll}
style={{
padding: '0.625rem 1.25rem',
backgroundColor: '#22c55e',
color: 'white',
border: 'none',
borderRadius: '6px',
fontSize: '0.9375rem',
fontWeight: 500,
cursor: 'pointer',
}}
>
Accept All Cookies
</button>
<button
onClick={rejectAll}
style={{
padding: '0.625rem 1.25rem',
backgroundColor: '#f5f5f5',
color: '#333',
border: '1px solid #ddd',
borderRadius: '6px',
fontSize: '0.9375rem',
fontWeight: 500,
cursor: 'pointer',
}}
>
Reject All
</button>
<button
onClick={() => setShowPreferences(true)}
style={{
padding: '0.625rem 1.25rem',
backgroundColor: 'transparent',
color: '#0066cc',
border: '1px solid #0066cc',
borderRadius: '6px',
fontSize: '0.9375rem',
fontWeight: 500,
cursor: 'pointer',
}}
>
Cookie Preferences
</button>
</div>
</div>
) : (
// Preferences panel
<div>
<h3 style={{ margin: '0 0 0.75rem 0', fontSize: '1.125rem', fontWeight: 600 }}>
Cookie Preferences
</h3>
<p style={{ margin: '0 0 1rem 0', color: '#555', fontSize: '0.875rem' }}>
Manage your cookie preferences below.
</p>
<div style={{ marginBottom: '1rem' }}>
{/* Functional Cookies */}
<div style={{
padding: '1rem',
backgroundColor: '#f9f9f9',
borderRadius: '8px',
marginBottom: '0.75rem',
border: '1px solid #e5e5e5'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '0.5rem' }}>
<div>
<h4 style={{ margin: 0, fontSize: '0.9375rem', fontWeight: 600 }}>Functional Cookies</h4>
<p style={{ margin: '0.25rem 0 0 0', fontSize: '0.8125rem', color: '#666' }}>
Essential for the website to function properly. Cannot be disabled.
</p>
</div>
<div style={{
padding: '0.25rem 0.75rem',
backgroundColor: '#e5e5e5',
color: '#666',
borderRadius: '4px',
fontSize: '0.75rem',
fontWeight: 500,
}}>
Always Active
</div>
</div>
</div>
{/* Analytics Cookies */}
<div style={{
padding: '1rem',
backgroundColor: '#fff',
borderRadius: '8px',
marginBottom: '0.75rem',
border: '1px solid #e5e5e5'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<h4 style={{ margin: 0, fontSize: '0.9375rem', fontWeight: 600 }}>Analytics Cookies</h4>
<p style={{ margin: '0.25rem 0 0 0', fontSize: '0.8125rem', color: '#666' }}>
Help us understand how visitors interact with our website.
</p>
</div>
<label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}>
<input
type="checkbox"
checked={consent.analytics}
onChange={(e) => updatePreference('analytics', e.target.checked)}
style={{ width: '18px', height: '18px', cursor: 'pointer' }}
/>
</label>
</div>
</div>
{/* Marketing Cookies */}
<div style={{
padding: '1rem',
backgroundColor: '#fff',
borderRadius: '8px',
marginBottom: '0.75rem',
border: '1px solid #e5e5e5'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<h4 style={{ margin: 0, fontSize: '0.9375rem', fontWeight: 600 }}>Marketing Cookies</h4>
<p style={{ margin: '0.25rem 0 0 0', fontSize: '0.8125rem', color: '#666' }}>
Used to track visitors across websites for advertising purposes.
</p>
</div>
<label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}>
<input
type="checkbox"
checked={consent.marketing}
onChange={(e) => updatePreference('marketing', e.target.checked)}
style={{ width: '18px', height: '18px', cursor: 'pointer' }}
/>
</label>
</div>
</div>
</div>
<div style={{ display: 'flex', gap: '0.75rem' }}>
<button
onClick={savePreferences}
style={{
padding: '0.625rem 1.25rem',
backgroundColor: '#0066cc',
color: 'white',
border: 'none',
borderRadius: '6px',
fontSize: '0.9375rem',
fontWeight: 500,
cursor: 'pointer',
}}
>
Save Preferences
</button>
<button
onClick={() => setShowPreferences(false)}
style={{
padding: '0.625rem 1.25rem',
backgroundColor: 'transparent',
color: '#666',
border: 'none',
borderRadius: '6px',
fontSize: '0.9375rem',
cursor: 'pointer',
}}
>
Back
</button>
</div>
</div>
)}
</div>
</div>
)
}