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:
462
skills/website-creator/templates/consent/CookieConsent.astro
Normal file
462
skills/website-creator/templates/consent/CookieConsent.astro
Normal file
@@ -0,0 +1,462 @@
|
||||
---
|
||||
// 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>
|
||||
99
skills/website-creator/templates/consent/README.md
Normal file
99
skills/website-creator/templates/consent/README.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# PDPA Consent Logging Template
|
||||
|
||||
Template สำหรับเพิ่ม PDPA consent logging ใน Next.js + Payload CMS (MongoDB)
|
||||
|
||||
## Files
|
||||
|
||||
```
|
||||
consent/
|
||||
├── collections/
|
||||
│ └── ConsentLogs.ts # Payload collection สำหรับ consent logs
|
||||
├── api/
|
||||
│ └── route.ts # API endpoint สำหรับบันทึก consent
|
||||
├── cookie-banner.tsx # CookieBanner component
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## วิธีใช้
|
||||
|
||||
### 1. เพิ่ม ConsentLogs Collection
|
||||
|
||||
Copy `collections/ConsentLogs.ts` ไปที่ `src/collections/` ของ project
|
||||
|
||||
### 2. สร้าง API Endpoint
|
||||
|
||||
Copy `api/route.ts` ไปที่ `src/app/api/consent/route.ts`
|
||||
|
||||
### 3. เพิ่ม CookieBanner Component
|
||||
|
||||
Copy `cookie-banner.tsx` ไปที่ `src/components/`
|
||||
|
||||
### 4. เพิ่มใน Layout
|
||||
|
||||
เพิ่ม `<CookieBanner />` ใน `src/app/(frontend)/layout.tsx`:
|
||||
|
||||
```tsx
|
||||
import { CookieBanner } from '@/components/cookie-banner'
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
{children}
|
||||
<CookieBanner />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 5. เพิ่ม Collection ใน payload.config.ts
|
||||
|
||||
```ts
|
||||
import ConsentLogs from './collections/ConsentLogs'
|
||||
|
||||
export default buildConfig({
|
||||
collections: [Users, Media, Snacks, Orders, ConsentLogs],
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### POST /api/consent
|
||||
|
||||
บันทึก consent action
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"action": "accept",
|
||||
"purpose": "all",
|
||||
"analytics": true,
|
||||
"marketing": false,
|
||||
"functional": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"doc": {
|
||||
"id": "...",
|
||||
"action": "accept",
|
||||
"purpose": "all",
|
||||
"analytics": true,
|
||||
"marketing": false,
|
||||
"functional": true,
|
||||
"userAgent": "Mozilla/5.0...",
|
||||
"ip": "127.0.0.1",
|
||||
"timestamp": "2026-04-10T00:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ⚠️ Pitfalls สำคัญ
|
||||
|
||||
1. **ใช้ `mongooseAdapter` ไม่ใช่ `mongodbAdapter`**
|
||||
2. **ConsentLogs ต้องใช้ `export default`** ไม่ใช่ named export
|
||||
81
skills/website-creator/templates/consent/api/consent.ts
Normal file
81
skills/website-creator/templates/consent/api/consent.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import type { APIRoute } from 'astro'
|
||||
|
||||
// POST /api/consent - บันทึก consent
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { session_id, essential, analytics, marketing, functional } = body
|
||||
|
||||
if (!session_id) {
|
||||
return new Response(JSON.stringify({ error: 'session_id is required' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
|
||||
// Get client info
|
||||
const ipAddress = request.headers.get('x-forwarded-for')?.split(',')[0] || 'unknown'
|
||||
const userAgent = request.headers.get('user-agent') || 'unknown'
|
||||
|
||||
// Build consent record
|
||||
const consentTypes = []
|
||||
if (essential) consentTypes.push('essential')
|
||||
if (analytics) consentTypes.push('analytics')
|
||||
if (marketing) consentTypes.push('marketing')
|
||||
if (functional) consentTypes.push('functional')
|
||||
|
||||
// In Payload CMS, you would save this to the consent-logs collection
|
||||
// For now, return success (Payload integration happens at build time)
|
||||
const record = {
|
||||
sessionId: session_id,
|
||||
consentType: consentTypes.length === 4 ? 'accept_all' : consentTypes.join(','),
|
||||
granted: analytics || marketing || functional,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
metadata: { essential, analytics, marketing, functional },
|
||||
createdAt: new Date().toISOString(),
|
||||
}
|
||||
|
||||
// Log for debugging (remove in production)
|
||||
console.log('[Consent API] New consent record:', JSON.stringify(record))
|
||||
|
||||
return new Response(JSON.stringify({ success: true, record }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('[Consent API] Error:', error)
|
||||
return new Response(JSON.stringify({ error: 'Internal server error' }), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/consent - ตรวจสอบ consent ของ session
|
||||
export const GET: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const url = new URL(request.url)
|
||||
const sessionId = url.searchParams.get('session_id')
|
||||
|
||||
if (!sessionId) {
|
||||
return new Response(JSON.stringify({ error: 'session_id is required' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
|
||||
// In Payload CMS, query the consent-logs collection
|
||||
// For now, return not found (Payload integration happens at build time)
|
||||
return new Response(JSON.stringify({ error: 'Not implemented in template' }), {
|
||||
status: 501,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('[Consent API] Error:', error)
|
||||
return new Response(JSON.stringify({ error: 'Internal server error' }), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import type { APIRoute } from 'astro'
|
||||
|
||||
// Right to be Forgotten API - PDPA Article 17
|
||||
// DELETE /api/consent?session_id=xxx - ลบข้อมูลของ session นี้
|
||||
|
||||
export const DELETE: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const url = new URL(request.url)
|
||||
const sessionId = url.searchParams.get('session_id')
|
||||
|
||||
if (!sessionId) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'session_id is required' }),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
}
|
||||
|
||||
// In Payload CMS, you would:
|
||||
// 1. Find all consent-logs with this sessionId
|
||||
// 2. Delete them
|
||||
// 3. Also delete any user data associated with this session
|
||||
|
||||
// Example Payload query (for reference):
|
||||
// await payload.delete({
|
||||
// collection: 'consent-logs',
|
||||
// where: { sessionId: { equals: sessionId } },
|
||||
// })
|
||||
|
||||
console.log(`[Right to be Forgotten] Deleting data for session: ${sessionId}`)
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: 'ข้อมูลของคุณถูกลบแล้ว',
|
||||
deletedAt: new Date().toISOString(),
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('[Right to be Forgotten] Error:', error)
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Internal server error' }),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/consent/export - ขอ export ข้อมูลของตัวเอง (PDPA Article 31)
|
||||
export const GET: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const url = new URL(request.url)
|
||||
const sessionId = url.searchParams.get('session_id')
|
||||
|
||||
if (!sessionId) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'session_id is required' }),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
}
|
||||
|
||||
// In Payload CMS, query consent-logs for this session
|
||||
// Return the data as JSON for the user to review
|
||||
|
||||
// Example Payload query (for reference):
|
||||
// const logs = await payload.find({
|
||||
// collection: 'consent-logs',
|
||||
// where: { sessionId: { equals: sessionId } },
|
||||
// })
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: 'ข้อมูลของคุณ',
|
||||
data: [], // Replace with actual Payload query result
|
||||
requestedAt: new Date().toISOString(),
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('[Consent Export] Error:', error)
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Internal server error' }),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
}
|
||||
}
|
||||
80
skills/website-creator/templates/consent/api/route.ts
Normal file
80
skills/website-creator/templates/consent/api/route.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getPayload } from 'payload'
|
||||
import config from '@/payload.config'
|
||||
|
||||
/**
|
||||
* POST /api/consent - Record consent action
|
||||
*
|
||||
* Request body:
|
||||
* {
|
||||
* action: 'accept' | 'reject' | 'update',
|
||||
* purpose: 'analytics' | 'marketing' | 'functional' | 'all',
|
||||
* analytics: boolean,
|
||||
* marketing: boolean,
|
||||
* functional: boolean,
|
||||
* previousConsent?: { analytics: boolean, marketing: boolean, functional: boolean }
|
||||
* }
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const payloadConfig = await config
|
||||
const payload = await getPayload({ config: payloadConfig })
|
||||
|
||||
const body = await request.json()
|
||||
const { action, purpose, analytics, marketing, functional, previousConsent } = body
|
||||
|
||||
// Validate required fields
|
||||
if (!action || !['accept', 'reject', 'update'].includes(action)) {
|
||||
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
||||
}
|
||||
if (!purpose || !['analytics', 'marketing', 'functional', 'all'].includes(purpose)) {
|
||||
return NextResponse.json({ error: 'Invalid purpose' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Get IP and User Agent
|
||||
const ip = request.headers.get('x-forwarded-for')?.split(',')[0]
|
||||
|| request.headers.get('x-real-ip')
|
||||
|| 'unknown'
|
||||
const userAgent = request.headers.get('user-agent') || 'unknown'
|
||||
|
||||
// Create consent log
|
||||
const consentLog = await payload.create({
|
||||
collection: 'consent-logs',
|
||||
data: {
|
||||
action,
|
||||
purpose,
|
||||
analytics: analytics ?? false,
|
||||
marketing: marketing ?? false,
|
||||
functional: functional ?? false,
|
||||
userAgent,
|
||||
ip,
|
||||
timestamp: new Date().toISOString(),
|
||||
previousConsent: previousConsent || null,
|
||||
newConsent: {
|
||||
analytics: analytics ?? false,
|
||||
marketing: marketing ?? false,
|
||||
functional: functional ?? false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({ success: true, doc: consentLog })
|
||||
} catch (error) {
|
||||
console.error('Consent logging error:', error)
|
||||
return NextResponse.json({ error: 'Failed to log consent' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/consent - Get current consent status (from cookie or localStorage)
|
||||
* This endpoint is mainly for verification, actual consent is stored client-side
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
// Consent is stored client-side in localStorage
|
||||
// This endpoint is for compliance verification
|
||||
return NextResponse.json({
|
||||
message: 'Consent is stored client-side',
|
||||
purposes: ['analytics', 'marketing', 'functional'],
|
||||
note: 'Use POST to update consent preferences'
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
import { CollectionConfig, Field } from 'payload'
|
||||
|
||||
// Consent Log Collection - เก็บ log การยินยอมของ users
|
||||
export const ConsentLog: CollectionConfig = {
|
||||
slug: 'consent-logs',
|
||||
admin: {
|
||||
useAsTitle: 'sessionId',
|
||||
defaultColumns: ['sessionId', 'consentType', 'granted', 'createdAt'],
|
||||
description: 'บันทึกการยินยอมของผู้ใช้ตาม PDPA',
|
||||
},
|
||||
access: {
|
||||
// ทุกคนสามารถสร้าง log ได้ (public)
|
||||
create: () => true,
|
||||
// แต่ดูได้เฉพาะ admin
|
||||
read: ({ req: { user } }) => {
|
||||
if (!user) return false
|
||||
return user.role === 'admin'
|
||||
},
|
||||
// แก้ไขได้เฉพาะ admin
|
||||
update: ({ req: { user } }) => {
|
||||
if (!user) return false
|
||||
return user.role === 'admin'
|
||||
},
|
||||
// ลบได้เฉพาะ admin
|
||||
delete: ({ req: { user } }) => {
|
||||
if (!user) return false
|
||||
return user.role === 'admin'
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'sessionId',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
description: 'Session ID ของผู้ใช้',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'consentType',
|
||||
type: 'select',
|
||||
required: true,
|
||||
options: [
|
||||
{ label: 'Essential', value: 'essential' },
|
||||
{ label: 'Analytics', value: 'analytics' },
|
||||
{ label: 'Marketing', value: 'marketing' },
|
||||
{ label: 'Functional', value: 'functional' },
|
||||
{ label: 'All Accepted', value: 'accept_all' },
|
||||
{ label: 'All Rejected', value: 'reject_all' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'granted',
|
||||
type: 'checkbox',
|
||||
required: true,
|
||||
defaultValue: false,
|
||||
admin: {
|
||||
description: 'ยินยอมหรือไม่',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ipAddress',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: 'IP Address ของผู้ใช้',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'userAgent',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: 'Browser User Agent',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'metadata',
|
||||
type: 'json',
|
||||
admin: {
|
||||
description: 'ข้อมูลเพิ่มเติม',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
type: 'date',
|
||||
required: true,
|
||||
admin: {
|
||||
description: 'วันที่และเวลาที่ยินยอม',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data }) => {
|
||||
// เพิ่ม timestamp อัตโนมัติ
|
||||
if (!data.createdAt) {
|
||||
data.createdAt = new Date().toISOString()
|
||||
}
|
||||
return data
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
// Consent Settings Collection - เก็บ settings ของ consent banner
|
||||
export const ConsentSettings: CollectionConfig = {
|
||||
slug: 'consent-settings',
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
description: 'ตั้งค่า Cookie Consent Banner',
|
||||
},
|
||||
access: {
|
||||
read: () => true, // Public read
|
||||
create: ({ req: { user } }) => !!user && user.role === 'admin',
|
||||
update: ({ req: { user } }) => !!user && user.role === 'admin',
|
||||
delete: ({ req: { user } }) => !!user && user.role === 'admin',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
defaultValue: 'นโยบายคุกกี้',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
required: true,
|
||||
defaultValue: 'เราใช้คุกกี้เพื่อปรับปรุงประสบการณ์การใช้งานเว็บไซต์ของคุณ คุณสามารถเลือกได้ว่าจะอนุญาตคุกกี้ประเภทใด',
|
||||
},
|
||||
{
|
||||
name: 'position',
|
||||
type: 'select',
|
||||
defaultValue: 'bottom',
|
||||
options: [
|
||||
{ label: 'ด้านล่าง (Bottom)', value: 'bottom' },
|
||||
{ label: 'ด้านบน (Top)', value: 'top' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'theme',
|
||||
type: 'select',
|
||||
defaultValue: 'light',
|
||||
options: [
|
||||
{ label: 'Light Mode', value: 'light' },
|
||||
{ label: 'Dark Mode', value: 'dark' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'essentialCookies',
|
||||
type: 'json',
|
||||
admin: {
|
||||
description: 'รายชื่อ essential cookies ที่จำเป็นต้องมี',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'analyticsCookies',
|
||||
type: 'json',
|
||||
admin: {
|
||||
description: 'รายชื่อ analytics cookies',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'marketingCookies',
|
||||
type: 'json',
|
||||
admin: {
|
||||
description: 'รายชื่อ marketing cookies',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'functionalCookies',
|
||||
type: 'json',
|
||||
admin: {
|
||||
description: 'รายชื่อ functional cookies',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isActive',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
admin: {
|
||||
description: 'แสดง consent banner หรือไม่',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export interface ConsentLogData {
|
||||
action: 'accept' | 'reject' | 'update'
|
||||
purpose: 'analytics' | 'marketing' | 'functional' | 'all'
|
||||
userAgent?: string
|
||||
ip?: string
|
||||
timestamp: string
|
||||
previousConsent?: Record<string, boolean>
|
||||
newConsent?: Record<string, boolean>
|
||||
}
|
||||
|
||||
const ConsentLogs: CollectionConfig = {
|
||||
slug: 'consent-logs',
|
||||
admin: {
|
||||
useAsTitle: 'timestamp',
|
||||
defaultColumns: ['timestamp', 'action', 'purpose', 'ip'],
|
||||
description: 'Log of all consent actions for PDPA compliance',
|
||||
},
|
||||
access: {
|
||||
create: () => true, // Allow anyone to create consent logs (public endpoint)
|
||||
read: () => true, // Allow reading for compliance purposes
|
||||
update: () => false, // Consent logs should not be modified
|
||||
delete: () => false, // Consent logs should not be deleted
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'action',
|
||||
type: 'select',
|
||||
required: true,
|
||||
options: [
|
||||
{ label: 'Accept', value: 'accept' },
|
||||
{ label: 'Reject', value: 'reject' },
|
||||
{ label: 'Update', value: 'update' },
|
||||
],
|
||||
admin: {
|
||||
description: 'The type of consent action',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'purpose',
|
||||
type: 'select',
|
||||
required: true,
|
||||
options: [
|
||||
{ label: 'Analytics', value: 'analytics' },
|
||||
{ label: 'Marketing', value: 'marketing' },
|
||||
{ label: 'Functional', value: 'functional' },
|
||||
{ label: 'All', value: 'all' },
|
||||
],
|
||||
admin: {
|
||||
description: 'The purpose of the consent',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'analytics',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
admin: {
|
||||
description: 'Consent for analytics cookies',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'marketing',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
admin: {
|
||||
description: 'Consent for marketing cookies',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'functional',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
admin: {
|
||||
description: 'Consent for functional cookies',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'userAgent',
|
||||
type: 'text',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
description: 'Browser user agent string',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ip',
|
||||
type: 'text',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
description: 'IP address of the user',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'timestamp',
|
||||
type: 'date',
|
||||
required: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
description: 'When the consent was given',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'previousConsent',
|
||||
type: 'json',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
description: 'Previous consent state (for updates)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'newConsent',
|
||||
type: 'json',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
description: 'New consent state',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default ConsentLogs
|
||||
316
skills/website-creator/templates/consent/cookie-banner.tsx
Normal file
316
skills/website-creator/templates/consent/cookie-banner.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user