Compare commits

..

57 Commits

Author SHA1 Message Date
OpenCode Agent
cfa8b4ba1a Grilles: rewrite FAQ to sound more natural in Thai
- เลือก ABS หรือ Aluminum ดี? (natural question form)
- ทำไม ABS ถึงไม่มีหยดน้ำขัง? (clearer explanation)
- สั่งทำขนาดพิเศษได้ไหม? (conversational tone)
2026-04-05 21:43:20 +07:00
OpenCode Agent
636b350878 Grilles: increase font sizes to match prose content
- Table text: text-sm → text-lg
- Size guide formula and list: text-sm → text-lg
- Material comparison lists: text-xs → text-lg
- FAQ items: text-sm → text-lg
- Installation steps: text-sm → text-lg
2026-04-05 21:26:50 +07:00
OpenCode Agent
48f60033ee Move mb-8 from paragraph to prose div for consistent spacing 2026-04-05 21:22:32 +07:00
OpenCode Agent
377da259e1 Fix Grilles: add padding before next section, fix Thai warnings
- Added mb-8 to ฟรี ไม่มีค่าใช้จ่าย paragraph
- Fixed ข้อควรระวัง: or→หรือ, static pressure→ความดันสถิต, шум→เสียงดัง
2026-04-05 21:17:03 +07:00
OpenCode Agent
0313ae80c4 Fix Grilles sections width - wrap in max-w-4xl container 2026-04-05 21:12:43 +07:00
OpenCode Agent
b93dda19de Phase 8: Grilles redesign - 2-column → 1-column alternating backgrounds
- Replaced 2-column grid with 1-column alternating bg design
- Technical specs, size guide, materials, FAQ as stacked sections
- Installation tips and gallery integrated into prose flow
- bg-gray-50 and bg-white alternating per design spec
2026-04-05 21:07:12 +07:00
OpenCode Agent
a13e9b947d Phase 10: Dukelarrsen page rewrite (E1-E2) 2026-04-05 20:05:23 +07:00
OpenCode Agent
88de9ff226 Phase 9: MECH Grooved page rewrite (D1-D3)
- Rewrote page with content from mech.co.th research
- Added: 5x faster installation, 25-40% cost savings stats
- Added: Ductile Iron material, 1-32 inch size, 225-750 psi pressure
- Added: Applications (Fire, Plumbing, HVAC, Industrial)
- Added: Quick Mech Coupling section with EPDM gasket info
- Added: Comparison table (Grooved vs Welding)
- Updated FAQ with MECH-specific answer
- Used standard product page template structure
2026-04-05 20:04:26 +07:00
OpenCode Agent
53188d622b Phase 8: Grilles page updates (C1-C6) 2026-04-05 20:04:09 +07:00
OpenCode Agent
e1c36a2434 Phase 7: PPR page installation steps fix (B1-B2) 2026-04-05 20:00:54 +07:00
OpenCode Agent
ef0b2a7415 Auto-sync from website-creator 2026-04-02 22:28:58 +07:00
OpenCode Agent
5501d70990 fix: Add max-w-4xl wrapper to FAQ and Installation sections in PPR page 2026-04-02 21:47:38 +07:00
OpenCode Agent
c96ebb3f2d fix: Redesign contact-us page with professional layout 2026-04-02 21:35:01 +07:00
OpenCode Agent
3971fe4aa4 fix: Simplify contact-us page - remove duplicate phone, remove follow us section 2026-04-02 21:10:05 +07:00
OpenCode Agent
ccec8a7c2f fix: Remove contact form and redesign contact-us page layout 2026-04-02 21:06:01 +07:00
OpenCode Agent
c643bf3b3a fix: Add max-w-4xl to Salt Spray Test section in vineman-2 2026-04-02 20:55:57 +07:00
OpenCode Agent
c589a62b25 fix: Add max-w-4xl to remaining sections in thermobreak page 2026-04-02 20:44:46 +07:00
OpenCode Agent
d1df5c01d4 fix: Add max-w-4xl mx-auto to sections for consistent width 2026-04-02 20:41:20 +07:00
OpenCode Agent
693c5e0904 fix: Add max-w-4xl mx-auto to sections in vineman-2 2026-04-02 20:35:49 +07:00
OpenCode Agent
5159445a51 fix: Add max-w-4xl mx-auto to sections for consistent width 2026-04-02 19:04:15 +07:00
Kunthawat
8ae6f412d9 fix: Replace Chinese text in water-pump FAQ 2026-04-02 18:42:15 +07:00
Kunthawat
7b7c428ff8 fix: Add missing LINE SVG icons to CTA section buttons
- Fixed 14 CTA sections that were missing LINE SVG icon
- All LINE buttons now show 'แชท [LINE icon]'
2026-04-02 14:02:36 +07:00
Kunthawat
27a56af25c fix: Add missing LINE SVG icons to all buttons
- Fixed 30 pages that had text without icon
- All LINE buttons now consistently show 'แชท [LINE icon]'
- Button order: text first, icon second
2026-04-02 13:41:05 +07:00
Kunthawat
97f066a9b3 fix: Remove text-white class from SVG elements 2026-04-02 13:12:30 +07:00
Kunthawat
93e2845b57 fix: Use inline SVG with text-white class for LINE icons
- Replaced img tags with inline SVG
- Use fill='currentColor' and class='text-white' for proper white color
- Icons now visible on both green and white button backgrounds
2026-04-02 13:08:36 +07:00
Kunthawat
70298a5ffb fix: Fix LINE button order and icon color in CTA sections
- CTA buttons: swap to 'แชท [LINE icon]' order
- All LINE icons: add white filter for visibility on green buttons
- Hero section icons: add white filter to match text color
2026-04-02 12:54:08 +07:00
Kunthawat
033acab1ff fix: Use CDN img for LINE icon instead of inline SVG
- Replaced problematic inline SVG with img tag from jsDelivr CDN
- More reliable rendering across browsers
- Added onerror fallback to hide if icon fails to load
- Fixed order: แชท [LINE icon]
2026-04-02 12:45:37 +07:00
Kunthawat
9541b42bbc fix: Correct Line button order - 'แชท [Line icon]'
- Swapped icon and text position in all Line buttons
- Now displays: แชท [LINE icon] instead of [LINE icon] แชท
- Fixed across all product page CTA and hero buttons
2026-04-02 12:34:52 +07:00
Kunthawat
9c1104aa5e fix: Standardize all Line buttons to 'แชท [Line icon]' pattern
- Updated CTA section buttons from 'Line: @dealplustech' to 'แชท [Line icon]'
- Updated hero section buttons from 'แชท Line' to 'แชท [Line icon]'
- 11 CTA buttons fixed across product pages
- 42 hero buttons fixed across 34 page files
- Added LINE SVG logo icon to all buttons
2026-04-02 11:55:49 +07:00
Kunthawat
82bae1ec17 Fix Line button - add proper SVG icon 2026-04-02 11:31:06 +07:00
Kunthawat
b1acd6aaef Add products-raw folder for ball-jet and tevada images 2026-04-02 11:24:06 +07:00
Kunthawat
6ecbc30920 Add root-level product images to git 2026-04-02 11:19:59 +07:00
Kunthawat
23574b1038 Add essential product images to git for deployment 2026-04-02 11:15:34 +07:00
Kunthawat
43e376d99b Add dealplustech-logo.png to git 2026-04-02 11:11:17 +07:00
Kunthawat
4424a30bee Update Dockerfile port to 3000 2026-04-02 10:44:40 +07:00
Kunthawat
4bb73d8924 Fix Dockerfile - use Node.js 22 instead of 20 for Astro 6 compatibility 2026-04-02 10:27:32 +07:00
Kunthawat
e1aaddc9e9 Revert to Tailwind v3 with @astrojs/tailwind - use --legacy-peer-deps for compatibility 2026-04-02 10:22:53 +07:00
Kunthawat
a2cd58e434 Fix npm install - add --legacy-peer-deps for Astro 6 + tailwind compatibility 2026-04-02 10:01:18 +07:00
Kunthawat
e44f1b176d Fix Dockerfile - use npm install instead of production to build native modules 2026-04-02 09:59:41 +07:00
Kunthawat
e1d170252b Revert to SSR mode with Node adapter - keep consent logs backend 2026-04-02 09:54:07 +07:00
Kunthawat
df2b00a914 Auto-sync from website-creator 2026-04-02 09:40:03 +07:00
Kunthawat
82e8a5fda7 Auto-sync from website-creator 2026-04-02 09:14:46 +07:00
Kunthawat
ec03a10712 Update privacy policy and terms of service with PDPA-compliant templates
Privacy Policy:
- Full PDPA compliance section (13 sections)
- Data collection: contact, order, payment info
- Legal bases: consent, contract, legal obligation, legitimate interest
- Cookie policy with consent requirements
- 72-hour breach notification
- PDPC contact info
- Children/minor protection (under 20)
- Company address: 338 หมู่ 3 ตำบลดอนตะโก อำเภอเมืองราชบุรี

Terms of Service:
- Clear service description: จำหน่ายอุปกรณ์ก่อสร้างทุกชนิด
- Product listing: PPR, HDPE, UPVC pipes, fittings, valves
- Order/payment terms
- Delivery info
- 7-day return policy
- Intellectual property
- Limitation of liability
- Thai law jurisdiction

Updated: เมษายน 2569
2026-04-01 20:44:26 +07:00
Kunthawat
397bc5a29b Fix refresh button styling - use bg-primary-500 instead of bg-primary 2026-04-01 17:47:36 +07:00
Kunthawat
88fcde1d62 Implement moreminimore-style consent backend with better-sqlite3
- Add @astrojs/node adapter for hybrid SSR mode
- Replace console logging with better-sqlite3 database storage
- Create data/ directory for consent.db persistence
- Full consent API: POST (log), GET (fetch), DELETE (remove)
- Admin dashboard at /admin/consent-logs.astro with:
  - Password auth via sessionStorage
  - Stats cards (total, analytics accepted, rejected, rate %)
  - 100 latest logs table
  - Export to CSV functionality
  - Delete individual records
- New Dockerfile: node:20-alpine + sqlite-libs runtime
- Admin password: Coolm@n1234mo

Note: Static pages remain prerendered, only API/admin routes are SSR.
2026-04-01 15:41:46 +07:00
Kunthawat
a1c9930d49 Fix build: simplify consent API to static-compatible console logging
- Remove [sessionId].ts dynamic route (requires adapter in static mode)
- Simplify consent API to log to console only (no SQLite/better-sqlite3)
- Fix syntax error in consent-logs page (curly brace escaping)
- Consent logs page works for viewing instructions (password: Coolm@n1234mo)

Note: In static mode, API routes cannot actually handle POST requests.
For full runtime consent logging, would need hybrid/SSR deployment.
2026-04-01 15:23:54 +07:00
Kunthawat
41bf954d80 Implement full consent logging system with SQLite database
- Install better-sqlite3 and @astrojs/node adapter
- Update consent API to use SQLite database
- Add DELETE endpoint for consent logs
- Update admin consent-logs page with full UI (stats, table, export, delete)
- Add sessionId to consent tracking
- Admin password: Coolm@n1234mo

Note: Database stored at data/consent.db (gitignored)
2026-04-01 15:09:16 +07:00
Kunthawat
8cce63bba3 Create admin consent-logs page with password protection
Password: Coolm@n1234mo

Note: Consent logs are written to server console/Docker logs.
For full persistence, database integration needed.
2026-04-01 14:59:44 +07:00
Kunthawat
07cdc0dce3 Fix consent API: handle empty/invalid JSON body gracefully 2026-04-01 14:52:20 +07:00
Kunthawat
a5b882e212 Rename products: ท่อไซเลอร์ → ท่อ Syler, ท่อระบายน้ำ 3 ชั้น → ท่อ XYLENT
- Update product pages, category page, header menu, homepage
2026-04-01 14:35:02 +07:00
Kunthawat
aac2bea8d9 Fix: Product pages content and HTML issues
- ท่อไซเลอร์: Move images after specs with 1 column layout
- ท่อและข้อต่อpvc: Remove empty image section
- เครื่องเชื่อม-hdpe: Add full content with grooved coupling info
- เม็กกรู๊ฟ-คับปลิ้ง: Add full content with benefits list
- grilles: Remove duplicate/overflow code from hero section
2026-04-01 09:58:24 +07:00
Kunthawat
8db13220dd Update ฉนวนหุ้มท่อ-pipe-insulation design to modern style
- Apply hero-gradient, card-glass, btn-white, cta-gradient
- Use section-title for consistent section headings
- Match product page design with category page style
2026-03-31 23:25:03 +07:00
Kunthawat
3935f373e9 Restore ฉนวนหุ้มท่อ-pipe-insulation content: product specs, price lists, applications 2026-03-31 23:19:06 +07:00
Kunthawat
6d41d59e53 Update Astro to latest version (5.18.1) 2026-03-31 22:56:11 +07:00
Kunthawat
9cddd3da57 Refactor: Update category structure, mega menu, footer, and remove unused pages
- Move DURGO from ระบบน้ำ to อุปกรณ์ปรับอากาศ
- Add -category suffix to อุปกรณ์ดับเพลิง and ฉนวนหุ้มท่อ category pages
- Update Header mega menu with correct category slugs
- Fix Footer layout: split categories to 2 columns, remove quick links
- Delete unused pages: all-projects, join-us, services, product
- All product images fixed to 1:1 aspect ratio
2026-03-31 22:54:54 +07:00
Kunthawat
dbbd9e22a2 Update product pages: รั้วเทวดา, ระบบรั้วไวน์แมน, หัวจ่ายแอร์-ball-jet, ฉนวนหุ้มท่อ - add original images and content 2026-03-31 10:03:39 +07:00
Kunthawat
7ee311ab02 Add PDF price lists to 7 product pages with ราคาสินค้า sections
- Added 11 PDF price list files to public/documents/
- Updated 7 product pages with downloadable PDF sections:
  - ฉนวนหุ้มท่อ-pipe-insulation (4 PDFs)
  - เทอร์โมเบรค-thermobreak (1 PDF)
  - ท่อhdpe (1 PDF)
  - เม็กกรู๊ฟ-คับปลิ้ง (1 PDF)
  - ท่อระบายน้ำ-3-ชั้น-ไซเลนท (2 PDFs)
  - ท่อพีพีอาร์ตราช้าง (1 PDF)
  - ท่อ-ppr-thai-ppr (1 PDF)
- Updated AGENTS.md files with PDF/document structure
2026-03-29 20:35:10 +07:00
616 changed files with 31626 additions and 5917 deletions

119
.astro/content.d.ts vendored
View File

@@ -15,21 +15,13 @@ declare module 'astro:content' {
[key: string]: unknown;
};
}
}
declare module 'astro:content' {
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
export type CollectionKey = keyof AnyEntryMap;
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
export type ContentCollectionKey = keyof ContentEntryMap;
export type DataCollectionKey = keyof DataEntryMap;
export type CollectionKey = keyof DataEntryMap;
export type CollectionEntry<C extends CollectionKey> = Flatten<DataEntryMap[C]>;
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
ContentEntryMap[C]
>['slug'];
export type ReferenceDataEntry<
C extends CollectionKey,
@@ -38,41 +30,17 @@ declare module 'astro:content' {
collection: C;
id: E;
};
export type ReferenceContentEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}) = string,
> = {
collection: C;
slug: E;
};
export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = {
collection: C;
id: string;
};
/** @deprecated Use `getEntry` instead. */
export function getEntryBySlug<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
// Note that this has to accept a regular string too, for SSR
entrySlug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
/** @deprecated Use `getEntry` instead. */
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
collection: C,
entryId: E,
): Promise<CollectionEntry<C>>;
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
export function getCollection<C extends keyof DataEntryMap, E extends CollectionEntry<C>>(
collection: C,
filter?: (entry: CollectionEntry<C>) => entry is E,
): Promise<E[]>;
export function getCollection<C extends keyof AnyEntryMap>(
export function getCollection<C extends keyof DataEntryMap>(
collection: C,
filter?: (entry: CollectionEntry<C>) => unknown,
): Promise<CollectionEntry<C>[]>;
@@ -84,14 +52,6 @@ declare module 'astro:content' {
import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>
>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
entry: ReferenceContentEntry<C, E>,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
@@ -100,15 +60,6 @@ declare module 'astro:content' {
): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
slug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
@@ -126,47 +77,52 @@ declare module 'astro:content' {
): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>;
/** Resolve an array of entry references from the same collection */
export function getEntries<C extends keyof ContentEntryMap>(
entries: ReferenceContentEntry<C, ValidContentEntrySlug<C>>[],
): Promise<CollectionEntry<C>[]>;
export function getEntries<C extends keyof DataEntryMap>(
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
): Promise<CollectionEntry<C>[]>;
export function render<C extends keyof AnyEntryMap>(
entry: AnyEntryMap[C][string],
export function render<C extends keyof DataEntryMap>(
entry: DataEntryMap[C][string],
): Promise<RenderResult>;
export function reference<C extends keyof AnyEntryMap>(
export function reference<
C extends
| keyof DataEntryMap
// Allow generic `string` to avoid excessive type errors in the config
// if `dev` is not running to update as you edit.
// Invalid collection names will be caught at build time.
| (string & {}),
>(
collection: C,
): import('astro/zod').ZodEffects<
): import('astro/zod').ZodPipe<
import('astro/zod').ZodString,
C extends keyof ContentEntryMap
? ReferenceContentEntry<C, ValidContentEntrySlug<C>>
: ReferenceDataEntry<C, keyof DataEntryMap[C]>
import('astro/zod').ZodTransform<
C extends keyof DataEntryMap
? {
collection: C;
id: string;
}
: never,
string
>
>;
// Allow generic `string` to avoid excessive type errors in the config
// if `dev` is not running to update as you edit.
// Invalid collection names will be caught at build time.
export function reference<C extends string>(
collection: C,
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
type InferEntrySchema<C extends keyof DataEntryMap> = import('astro/zod').infer<
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
>;
type ContentEntryMap = {
};
type ExtractLoaderConfig<T> = T extends { loader: infer L } ? L : never;
type InferLoaderSchema<
C extends keyof DataEntryMap,
L = ExtractLoaderConfig<ContentConfig['collections'][C]>,
> = L extends { schema: import('astro/zod').ZodSchema }
? import('astro/zod').infer<L['schema']>
: any;
type DataEntryMap = {
"blog": Record<string, {
id: string;
render(): Render[".md"];
slug: string;
body: string;
body?: string;
collection: "blog";
data: any;
rendered?: RenderedContent;
@@ -174,9 +130,7 @@ declare module 'astro:content' {
}>;
"products": Record<string, {
id: string;
render(): Render[".md"];
slug: string;
body: string;
body?: string;
collection: "products";
data: any;
rendered?: RenderedContent;
@@ -185,8 +139,6 @@ declare module 'astro:content' {
};
type AnyEntryMap = ContentEntryMap & DataEntryMap;
type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader<
infer TData,
infer TEntryFilter,
@@ -195,7 +147,6 @@ declare module 'astro:content' {
>
? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError }
: { data: never; entryFilter: never; collectionFilter: never; error: never };
type ExtractDataType<T> = ExtractLoaderTypes<T>['data'];
type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];

0
.astro/content.db Normal file
View File

View File

@@ -1 +1 @@
[["Map",1,2],"meta::meta",["Map",3,4,5,6,7,8],"astro-version","5.18.1","content-config-digest","2cc56fd17be92673","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"site\":\"https://dealplustech.co.th\",\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"i18n\":{\"defaultLocale\":\"th\",\"locales\":[\"th\"],\"routing\":{\"prefixDefaultLocale\":false,\"redirectToDefaultLocale\":false,\"fallbackType\":\"redirect\"}},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[],\"actionBodySizeLimit\":1048576},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false,\"svgo\":false},\"legacy\":{\"collections\":false}}"]
[["Map",1,2],"meta::meta",["Map",3,4,5,6,7,8],"astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"site\":\"https://dealplustech.co.th\",\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":false,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\",\"entrypoint\":\"astro/assets/endpoint/dev\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":{\"backticks\":true,\"closingQuotes\":{\"double\":\"”\",\"single\":\"\"},\"dashes\":true,\"ellipses\":true,\"openingQuotes\":{\"double\":\"“\",\"single\":\"\"},\"quotes\":true}},\"i18n\":{\"defaultLocale\":\"th\",\"locales\":[\"th\"],\"routing\":{\"prefixDefaultLocale\":false,\"redirectToDefaultLocale\":false,\"fallbackType\":\"redirect\"}},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[],\"csp\":false,\"actionBodySizeLimit\":1048576,\"serverIslandBodySizeLimit\":1048576},\"env\":{\"schema\":{},\"validateSecrets\":false},\"prerenderConflictBehavior\":\"warn\",\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"chromeDevtoolsWorkspace\":false,\"svgo\":false,\"rustCompiler\":false,\"queuedRendering\":{\"enabled\":false}},\"legacy\":{\"collectionsBackwardsCompat\":false},\"session\":{\"driver\":{\"entrypoint\":\"unstorage/drivers/fs-lite\",\"config\":{\"base\":\"/Users/kunthawatgreethong/Gitea/dealplustech-new/dealplustech-astro/node_modules/.astro/sessions\"}}}}","astro-version","6.1.2","content-config-digest","2cc56fd17be92673"]

4
.astro/integrations/astro_db/db.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
// This file is generated by Astro DB
declare module 'astro:db' {
}

View File

@@ -1,5 +1,5 @@
{
"_variables": {
"lastUpdateCheck": 1773885716458
"lastUpdateCheck": 1774930788095
}
}

20
.gitignore vendored
View File

@@ -2,6 +2,21 @@
public/images/
!public/images/logo/
!public/images/favicon.*
!public/images/products-cropped/
!public/images/products-misc/
!public/images/thermobreak/
!public/images/grilles/
!public/images/groove-coupling/
!public/images/mech/
!public/images/dukelarrsen/
!public/images/durgo-avvs/
!public/images/pipe-coupling/
!public/images/ppr/
!public/images/realflex/
!public/images/poloplast/
!public/images/products-raw/
!public/images/*.jpg
!public/images/*.png
# Build output
dist/
@@ -13,6 +28,11 @@ node_modules/
*.log
npm-debug.log*
# Environment
.env
.env.*
!.env.example
# OS files
.DS_Store
Thumbs.db

90
AGENTS.md Normal file
View File

@@ -0,0 +1,90 @@
# dealplustech-astro/
**Generated:** 2026-03-27
**Branch:** main
## OVERVIEW
Astro 5.x Thai industrial e-commerce site (dealplustech.co.th). Migrated from WordPress. PDPA-compliant cookie consent, Umami analytics, 63 pages, 45 Thai-named routes.
## STRUCTURE
```
dealplustech-astro/
├── src/
│ ├── components/ # .astro components (common/, consent/, product/, ui/)
│ ├── content/ # Astro content collections (blog/, products/) - EMPTY
│ ├── layouts/ # BaseLayout.astro (root HTML shell)
│ ├── pages/ # 45 Thai-named static routes + index.astro
│ ├── styles/ # global.css
│ ├── images/ # ppr-tables/ (product reference images)
│ └── lib/ # EMPTY
├── public/
│ ├── images/ # All product/corporate images (dist/, public/ mirrored)
│ └── documents/ # PDF price lists (11 PDFs linked from product pages)
├── db/ # EMPTY (SQLite/Turso for consent logs at runtime)
├── astro.config.mjs # Astro config: site URL, i18n (th only), sitemap
├── tailwind.config.js # Kanit font, custom colors (primary green, accent orange)
├── Dockerfile # Multi-stage: node:20-alpine → nginx:alpine
└── package.json # Scripts: dev, build, preview, db:push, db:seed
```
## WHERE TO LOOK
| Task | Location | Notes |
|------|----------|-------|
| Global layout | `src/layouts/BaseLayout.astro` | HTML shell, SEO meta, cookie consent |
| Homepage | `src/pages/index.astro` | 13KB - main entry |
| Product pages | `src/pages/[thai-url]/index.astro` | 45 static routes |
| Header/Footer | `src/components/common/` | Header.astro, Footer.astro |
| Cookie consent | `src/components/consent/` | EMPTY - handled via astro-consent |
| API routes | `src/pages/api/` | EMPTY - no API routes configured |
| Content config | `src/content.config.ts` | Content collections definition |
## CONVENTIONS
- **Thai URLs only** — Never use English URLs for routes
- **Astro components** — `.astro` files with frontmatter imports
- **Kanit font** — Thai-optimized, set in tailwind.config.js
- **Cookie consent** — astro-consent package, blocks analytics until accepted
- **Umami analytics** — Requires `UMAMI_WEBSITE_ID` env var
- **Admin password** — Required for consent log access at `/admin/consent-logs`
## ANTI-PATTERNS (THIS PROJECT)
- Do NOT add TypeScript entry points (no `src/main.ts`)
- Do NOT use English route names — SEO depends on Thai URLs
- Do NOT modify `dist/` directly — it's gitignored build output
- Do NOT run crawlers from Python in production environment
- Do NOT commit `.env` files
## UNIQUE STYLES
- **Custom Tailwind colors**: primary (green #3f8b6d), accent (orange #e35c18)
- **PDPA compliance**: 14-disclosure privacy policy, granular cookie consent
- **Custom animations**: fade-in, slide-up, slide-down, scale-in
- **i18n**: Thai-only site, `prefixDefaultLocale: false`
- **Image organization**: Mirrored `public/images/` and `dist/images/`
## COMMANDS
```bash
cd dealplustech-astro
npm install # Install dependencies
npm run dev # Dev server at localhost:4321
npm run build # Production build → dist/
npm run preview # Preview built site
# Database
npm run db:push # Push schema to SQLite/Turso
npm run db:seed # Seed database
```
## NOTES
- **No tests** — `package.json` has no test script
- **Easypanel deploy** — Auto-deploys on push to main, uses `npm install` then `npm run build`
- **Image pipeline** — Python `image_processor.py` processes downloaded images
- **Consent logs** — SQLite at `db/consent.db` (gitignored), or Turso if configured
- **PDF price lists** — 11 PDFs in `public/documents/`, linked from 7 product pages with "ราคาสินค้า" section

View File

@@ -1,11 +1,23 @@
FROM node:20-alpine AS build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
RUN npm install --legacy-peer-deps
COPY . .
RUN npm run build
FROM nginx:alpine AS runtime
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --legacy-peer-deps
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
RUN apk add --no-cache sqlite-libs
EXPOSE 3000
ENV NODE_ENV=production
ENV HOST=0.0.0.0
ENV PORT=3000
CMD ["node", "dist/server/entry.mjs"]

View File

@@ -244,8 +244,8 @@ Edit product pages in `src/pages/[url]/index.astro`
**Deal Plus Tech**
- Phone: 090-555-1415
- Line: @dealplustech
- Email: info@dealplustech.co.th
- Line: @JPPSELECTION
- Email: info@JPPSELECTION.co.th
## 📄 License

View File

@@ -1,15 +1,21 @@
// @ts-check
import { defineConfig } from 'astro/config';
import tailwind from '@astrojs/tailwind';
import sitemap from '@astrojs/sitemap';
import node from '@astrojs/node';
export default defineConfig({
site: 'https://dealplustech.co.th',
adapter: node({
mode: 'standalone'
}),
integrations: [
tailwind({
applyBaseStyles: true,
}),
sitemap(),
],
output: 'server',
i18n: {
defaultLocale: 'th',
locales: ['th'],
@@ -27,4 +33,4 @@ export default defineConfig({
cssMinify: true,
},
},
});
});

0
data/.gitkeep Normal file
View File

BIN
data/consent.db Normal file

Binary file not shown.

28
nginx.conf Normal file
View File

@@ -0,0 +1,28 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript application/json;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# SPA fallback - all paths serve index.html
location / {
try_files $uri $uri/ /index.html;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}

2181
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,15 +10,18 @@
"db:seed": "node db/seed.js"
},
"dependencies": {
"@astrojs/db": "^0.14.0",
"@astrojs/sitemap": "^3.2.0",
"@astrojs/db": "^0.20.1",
"@astrojs/node": "^10.0.4",
"@astrojs/sitemap": "^3.7.2",
"@astrojs/tailwind": "^5.1.4",
"astro": "^5.1.1",
"astro": "^6.1.2",
"astro-consent": "^1.0.0",
"better-sqlite3": "^12.8.0",
"drizzle-orm": "^0.38.2",
"tailwindcss": "^3.4.17"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.13",
"@types/node": "^22.10.2",
"typescript": "^5.7.2"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Some files were not shown because too many files have changed in this diff Show More