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
463 lines
13 KiB
Plaintext
463 lines
13 KiB
Plaintext
---
|
|
// CookieConsent.astro - PDPA Cookie Consent Banner
|
|
// ทำงานจริง: ถ้า reject จะไม่ load tracking scripts
|
|
|
|
interface Props {
|
|
position?: 'bottom' | 'top';
|
|
theme?: 'light' | 'dark';
|
|
}
|
|
|
|
const { position = 'bottom', theme = 'light' } = Astro.props;
|
|
|
|
// Consent states
|
|
const CONSENT_TYPES = {
|
|
ESSENTIAL: 'essential',
|
|
ANALYTICS: 'analytics',
|
|
MARKETING: 'marketing',
|
|
FUNCTIONAL: 'functional',
|
|
} as const;
|
|
---
|
|
|
|
<div id="cookie-consent-banner" class={`cookie-consent cookie-consent--${position} cookie-consent--${theme}`} hidden>
|
|
<div class="cookie-consent__content">
|
|
<div class="cookie-consent__text">
|
|
<h3 class="cookie-consent__title">นโยบายคุกกี้</h3>
|
|
<p class="cookie-consent__description">
|
|
เราใช้คุกกี้เพื่อปรับปรุงประสบการณ์การใช้งานเว็บไซต์ของคุณ
|
|
คุณสามารถเลือกได้ว่าจะอนุญาตคุกกี้ประเภทใด
|
|
<a href="/privacy-policy" target="_blank">อ่านนโยบายความเป็นส่วนตัว</a>
|
|
</p>
|
|
</div>
|
|
|
|
<div class="cookie-consent__categories">
|
|
<div class="cookie-consent__category">
|
|
<div class="cookie-consent__category-header">
|
|
<span class="cookie-consent__category-name">คุกกี้ที่จำเป็น</span>
|
|
<span class="cookie-consent__badge cookie-consent__badge--required">จำเป็นเสมอ</span>
|
|
</div>
|
|
<p class="cookie-consent__category-desc">ใช้สำหรับการทำงานพื้นฐานของเว็บไซต์ ไม่สามารถปิดได้</p>
|
|
</div>
|
|
|
|
<div class="cookie-consent__category">
|
|
<div class="cookie-consent__category-header">
|
|
<span class="cookie-consent__category-name">คุกกี้วิเคราะห์</span>
|
|
<label class="cookie-consent__toggle">
|
|
<input type="checkbox" id="consent-analytics" checked />
|
|
<span class="cookie-consent__toggle-slider"></span>
|
|
</label>
|
|
</div>
|
|
<p class="cookie-consent__category-desc">ช่วยให้เราเข้าใจพฤติกรรมการใช้งานเว็บไซต์</p>
|
|
</div>
|
|
|
|
<div class="cookie-consent__category">
|
|
<div class="cookie-consent__category-header">
|
|
<span class="cookie-consent__category-name">คุกกี้การตลาด</span>
|
|
<label class="cookie-consent__toggle">
|
|
<input type="checkbox" id="consent-marketing" checked />
|
|
<span class="cookie-consent__toggle-slider"></span>
|
|
</label>
|
|
</div>
|
|
<p class="cookie-consent__category-desc">ใช้สำหรับแสดงโฆษณาที่ตรงกับความสนใจของคุณ</p>
|
|
</div>
|
|
|
|
<div class="cookie-consent__category">
|
|
<div class="cookie-consent__category-header">
|
|
<span class="cookie-consent__category-name">คุกกี้ฟังก์ชัน</span>
|
|
<label class="cookie-consent__toggle">
|
|
<input type="checkbox" id="consent-functional" checked />
|
|
<span class="cookie-consent__toggle-slider"></span>
|
|
</label>
|
|
</div>
|
|
<p class="cookie-consent__category-desc">ช่วยจดจำการตั้งค่าของคุณ</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="cookie-consent__actions">
|
|
<button id="cookie-consent-accept-all" class="cookie-consent__btn cookie-consent__btn--primary">
|
|
ยอมรับทั้งหมด
|
|
</button>
|
|
<button id="cookie-consent-reject-all" class="cookie-consent__btn cookie-consent__btn--secondary">
|
|
ปฏิเสธทั้งหมด
|
|
</button>
|
|
<button id="cookie-consent-save" class="cookie-consent__btn cookie-consent__btn--outline">
|
|
บันทึกการตั้งค่า
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.cookie-consent {
|
|
position: fixed;
|
|
left: 0;
|
|
right: 0;
|
|
z-index: 9999;
|
|
background: var(--color-bg, #ffffff);
|
|
border-top: 1px solid var(--color-border, #e5e7eb);
|
|
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
|
padding: 1.5rem;
|
|
font-family: 'Kanit', 'Noto Sans Thai', system-ui, sans-serif;
|
|
}
|
|
|
|
.cookie-consent--bottom {
|
|
bottom: 0;
|
|
}
|
|
|
|
.cookie-consent--top {
|
|
top: 0;
|
|
}
|
|
|
|
.cookie-consent[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
.cookie-consent__content {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.cookie-consent__title {
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
margin: 0 0 0.5rem 0;
|
|
color: var(--color-text, #111827);
|
|
}
|
|
|
|
.cookie-consent__description {
|
|
font-size: 0.875rem;
|
|
color: var(--color-text-secondary, #6b7280);
|
|
margin: 0 0 1rem 0;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.cookie-consent__description a {
|
|
color: var(--color-primary, #3b82f6);
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.cookie-consent__categories {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.cookie-consent__category {
|
|
background: var(--color-bg-secondary, #f9fafb);
|
|
border-radius: 0.5rem;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.cookie-consent__category-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.cookie-consent__category-name {
|
|
font-weight: 500;
|
|
color: var(--color-text, #111827);
|
|
}
|
|
|
|
.cookie-consent__badge {
|
|
font-size: 0.75rem;
|
|
padding: 0.125rem 0.5rem;
|
|
border-radius: 9999px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.cookie-consent__badge--required {
|
|
background: var(--color-primary, #3b82f6);
|
|
color: white;
|
|
}
|
|
|
|
.cookie-consent__category-desc {
|
|
font-size: 0.8125rem;
|
|
color: var(--color-text-secondary, #6b7280);
|
|
margin: 0;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.cookie-consent__toggle {
|
|
position: relative;
|
|
display: inline-block;
|
|
width: 44px;
|
|
height: 24px;
|
|
}
|
|
|
|
.cookie-consent__toggle input {
|
|
opacity: 0;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
|
|
.cookie-consent__toggle-slider {
|
|
position: absolute;
|
|
cursor: pointer;
|
|
inset: 0;
|
|
background: var(--color-border, #d1d5db);
|
|
border-radius: 24px;
|
|
transition: 0.3s;
|
|
}
|
|
|
|
.cookie-consent__toggle-slider::before {
|
|
position: absolute;
|
|
content: "";
|
|
height: 18px;
|
|
width: 18px;
|
|
left: 3px;
|
|
bottom: 3px;
|
|
background: white;
|
|
border-radius: 50%;
|
|
transition: 0.3s;
|
|
}
|
|
|
|
.cookie-consent__toggle input:checked + .cookie-consent__toggle-slider {
|
|
background: var(--color-primary, #3b82f6);
|
|
}
|
|
|
|
.cookie-consent__toggle input:checked + .cookie-consent__toggle-slider::before {
|
|
transform: translateX(20px);
|
|
}
|
|
|
|
.cookie-consent__toggle input:disabled + .cookie-consent__toggle-slider {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.cookie-consent__actions {
|
|
display: flex;
|
|
gap: 0.75rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.cookie-consent__btn {
|
|
padding: 0.625rem 1.25rem;
|
|
border-radius: 0.5rem;
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
font-family: inherit;
|
|
border: none;
|
|
}
|
|
|
|
.cookie-consent__btn--primary {
|
|
background: var(--color-primary, #3b82f6);
|
|
color: white;
|
|
}
|
|
|
|
.cookie-consent__btn--primary:hover {
|
|
background: var(--color-primary-dark, #2563eb);
|
|
}
|
|
|
|
.cookie-consent__btn--secondary {
|
|
background: var(--color-text, #111827);
|
|
color: white;
|
|
}
|
|
|
|
.cookie-consent__btn--secondary:hover {
|
|
background: var(--color-text-dark, #000000);
|
|
}
|
|
|
|
.cookie-consent__btn--outline {
|
|
background: transparent;
|
|
color: var(--color-text, #111827);
|
|
border: 1px solid var(--color-border, #d1d5db);
|
|
}
|
|
|
|
.cookie-consent__btn--outline:hover {
|
|
background: var(--color-bg-secondary, #f9fafb);
|
|
}
|
|
|
|
/* Dark theme */
|
|
.cookie-consent--dark {
|
|
--color-bg: #1f2937;
|
|
--color-bg-secondary: #374151;
|
|
--color-border: #4b5563;
|
|
--color-text: #f9fafb;
|
|
--color-text-secondary: #d1d5db;
|
|
--color-primary: #60a5fa;
|
|
--color-primary-dark: #3b82f6;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.cookie-consent {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.cookie-consent__actions {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.cookie-consent__btn {
|
|
width: 100%;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
// Consent Manager
|
|
class ConsentManager {
|
|
private readonly CONSENT_KEY = 'cookie_consent';
|
|
private readonly API_URL = '/api/consent';
|
|
|
|
async init() {
|
|
// Check if consent already given
|
|
const existing = this.getStoredConsent();
|
|
if (!existing) {
|
|
this.showBanner();
|
|
} else {
|
|
this.applyConsent(existing);
|
|
}
|
|
|
|
// Bind event listeners
|
|
this.bindEvents();
|
|
}
|
|
|
|
private bindEvents() {
|
|
const acceptAll = document.getElementById('cookie-consent-accept-all');
|
|
const rejectAll = document.getElementById('cookie-consent-reject-all');
|
|
const save = document.getElementById('cookie-consent-save');
|
|
|
|
acceptAll?.addEventListener('click', () => this.acceptAll());
|
|
rejectAll?.addEventListener('click', () => this.rejectAll());
|
|
save?.addEventListener('click', () => this.saveCustom());
|
|
}
|
|
|
|
private showBanner() {
|
|
const banner = document.getElementById('cookie-consent-banner');
|
|
banner?.removeAttribute('hidden');
|
|
}
|
|
|
|
private hideBanner() {
|
|
const banner = document.getElementById('cookie-consent-banner');
|
|
banner?.setAttribute('hidden', '');
|
|
}
|
|
|
|
private getStoredConsent(): Record<string, boolean> | null {
|
|
const stored = localStorage.getItem(this.CONSENT_KEY);
|
|
return stored ? JSON.parse(stored) : null;
|
|
}
|
|
|
|
private async saveConsent(consent: Record<string, boolean>) {
|
|
// Save to localStorage
|
|
localStorage.setItem(this.CONSENT_KEY, JSON.stringify(consent));
|
|
|
|
// Send to server
|
|
try {
|
|
await fetch(this.API_URL, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
...consent,
|
|
session_id: this.getSessionId(),
|
|
}),
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to save consent:', error);
|
|
}
|
|
|
|
// Apply consent
|
|
this.applyConsent(consent);
|
|
this.hideBanner();
|
|
}
|
|
|
|
private async acceptAll() {
|
|
const consent = {
|
|
essential: true,
|
|
analytics: true,
|
|
marketing: true,
|
|
functional: true,
|
|
};
|
|
await this.saveConsent(consent);
|
|
}
|
|
|
|
private async rejectAll() {
|
|
const consent = {
|
|
essential: true, // Always required
|
|
analytics: false,
|
|
marketing: false,
|
|
functional: false,
|
|
};
|
|
await this.saveConsent(consent);
|
|
}
|
|
|
|
private async saveCustom() {
|
|
const consent = {
|
|
essential: true, // Always required
|
|
analytics: (document.getElementById('consent-analytics') as HTMLInputElement)?.checked ?? false,
|
|
marketing: (document.getElementById('consent-marketing') as HTMLInputElement)?.checked ?? false,
|
|
functional: (document.getElementById('consent-functional') as HTMLInputElement)?.checked ?? false,
|
|
};
|
|
await this.saveConsent(consent);
|
|
}
|
|
|
|
private applyConsent(consent: Record<string, boolean>) {
|
|
// Essential cookies - always on (handled by server)
|
|
|
|
// Analytics
|
|
if (consent.analytics) {
|
|
this.enableAnalytics();
|
|
} else {
|
|
this.disableAnalytics();
|
|
}
|
|
|
|
// Marketing
|
|
if (consent.marketing) {
|
|
this.enableMarketing();
|
|
} else {
|
|
this.disableMarketing();
|
|
}
|
|
|
|
// Functional
|
|
if (consent.functional) {
|
|
this.enableFunctional();
|
|
} else {
|
|
this.disableFunctional();
|
|
}
|
|
}
|
|
|
|
private enableAnalytics() {
|
|
// Enable GA4 etc.
|
|
window.dispatchEvent(new CustomEvent('consent:analytics:Granted'));
|
|
}
|
|
|
|
private disableAnalytics() {
|
|
// Disable GA4, clear existing cookies
|
|
window.dispatchEvent(new CustomEvent('consent:analytics:Denied'));
|
|
}
|
|
|
|
private enableMarketing() {
|
|
window.dispatchEvent(new CustomEvent('consent:marketing:Granted'));
|
|
}
|
|
|
|
private disableMarketing() {
|
|
window.dispatchEvent(new CustomEvent('consent:marketing:Denied'));
|
|
}
|
|
|
|
private enableFunctional() {
|
|
window.dispatchEvent(new CustomEvent('consent:functional:Granted'));
|
|
}
|
|
|
|
private disableFunctional() {
|
|
window.dispatchEvent(new CustomEvent('consent:functional:Denied'));
|
|
}
|
|
|
|
private getSessionId(): string {
|
|
let sessionId = sessionStorage.getItem('session_id');
|
|
if (!sessionId) {
|
|
sessionId = crypto.randomUUID();
|
|
sessionStorage.setItem('session_id', sessionId);
|
|
}
|
|
return sessionId;
|
|
}
|
|
}
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
new ConsentManager().init();
|
|
});
|
|
</script>
|