[ กลับหน้าราคาทอง // HOME ]
GOLDPRICE // CODEBASE

โครงสร้างโค้ดและตัวอ่านซอร์ส

ภาพรวมทั้งโปรเจกต์: ผังไฟล์จริง เทคโนโลยีที่ใช้ และตัวอ่านซอร์สโค้ดที่ดูและดาวน์โหลดได้ทุกไฟล์

01

ผังไฟล์ // File tree

goldprice/
├─ public/                fonts, favicon, og.jpg, manifest, robots
├─ src/
│  ├─ lib/
│  │  ├─ gold.ts          fetch + parse the Thai gold feed (server fn)
│  │  └─ source.ts        read project files for the viewer (server fn)
│  ├─ routes/
│  │  ├─ __root.tsx       app shell, <head>, theme, favicon, OG meta
│  │  ├─ index.tsx        home - the live price HUD
│  │  ├─ source.tsx       /source - data sources & parsing
│  │  ├─ architecture.tsx /architecture - server fetch flow
│  │  ├─ codebase.tsx     /codebase - this page
│  │  └─ api/og.tsx       dynamic OpenGraph link-preview card
│  ├─ components/
│  │  └─ docs.tsx         shared docs UI (nav, sections, cards)
│  ├─ router.tsx          TanStack Router wiring
│  ├─ routeTree.gen.ts    generated route tree (do not edit)
│  ├─ styles.css          Tailwind entry + cyberpunk theme
│  └─ poke-defaults.css   baked fonts + base tokens
├─ package.json           scripts + dependencies
├─ vite.config.ts         build config
└─ tsconfig.json          TypeScript config
02

เทคโนโลยีที่ใช้ // Tech stack

FrameworkTanStack Start (React 19 + TanStack Router)
LanguageTypeScript
Build / serverVite + Nitro
StylingTailwind CSS v4 (pure-black / gold theme)
Iconslucide-react
FontsExposure (display) · OpenRunde (UI) · JetBrains Mono (numbers)
Data fetchServer function (createServerFn) - direct fetch, no proxy
Data sourcethaigold.info realtime JSON feed
DatabaseNone - prices are fetched live, nothing is persisted
03

ตัวอ่านซอร์สโค้ด // Source viewer

เลือกไฟล์เพื่อดูเนื้อหาจริงจากโปรเจกต์ กดปุ่มดาวน์โหลดเพื่อบันทึกไฟล์ ที่กำลังดูอยู่

src/lib/gold.ts
import { createServerFn } from "@tanstack/react-start";

// Source of record for Thai gold pricing - the same realtime feed the original
// gold-price-th-tracker pulls from, so pricing stays in parity.
const FEED_URL = "https://thaigold.info/RealTimeDataV2/gtdata_.txt";

type FeedRow = {
  name: string;
  bid: string | number;
  ask: string | number;
  diff: string | number;
};

export type Quote = {
  bid: number | null;
  ask: number | null;
  diff: number | null;
};

export type GoldData = {
  goldBar: Quote; // ทองคำแท่ง 96.5%
  goldJewelry: Quote; // ทองรูปพรรณ 96.5%
  spot: Quote; // XAU/USD ($/oz)
  thb: Quote; // USD/THB
  updatedAt: number; // epoch ms
  ok: boolean;
};

function toNum(v: string | number | null | undefined): number | null {
  if (v === null || v === undefined) return null;
  if (typeof v === "number") return Number.isFinite(v) ? v : null;
  const cleaned = v.toString().replace(/[+,\s]/g, "");
  if (cleaned === "") return null;
  const n = Number(cleaned);
  return Number.isFinite(n) ? n : null;
}

function quote(row: FeedRow | undefined): Quote {
  if (!row) return { bid: null, ask: null, diff: null };
  return { bid: toNum(row.bid), ask: toNum(row.ask), diff: toNum(row.diff) };
}

function parseFeed(rows: FeedRow[]): GoldData {
  const byName = new Map<string, FeedRow>();
  for (const r of rows) byName.set(r.name, r);

  // "สมาคมฯ" = gold bar 96.5% (ทองคำแท่ง); "96.5%" = jewelry (ทองรูปพรรณ).
  const goldBar = quote(byName.get("สมาคมฯ"));
  const goldJewelry = quote(byName.get("96.5%"));
  const spot = quote(byName.get("GoldSpot"));
  const thb = quote(byName.get("THB"));

  // The "Update" row carries the feed timestamp in its bid (unix seconds).
  const update = byName.get("Update");
  const ts = toNum(update?.bid);
  const updatedAt = ts ? ts * 1000 : Date.now();

  return {
    goldBar,
    goldJewelry,
    spot,
    thb,
    updatedAt,
    ok: goldBar.ask !== null || goldJewelry.ask !== null,
  };
}

export const getGoldData = createServerFn({ method: "GET" }).handler(
  async (): Promise<GoldData> => {
    try {
      const res = await fetch(FEED_URL, {
        signal: AbortSignal.timeout(9000),
        headers: { Accept: "application/json, text/plain, */*" },
      });
      if (!res.ok) throw new Error(`feed ${res.status}`);
      const text = await res.text();
      const rows = JSON.parse(text) as FeedRow[];
      if (!Array.isArray(rows)) throw new Error("bad feed shape");
      return parseFeed(rows);
    } catch {
      return {
        goldBar: { bid: null, ask: null, diff: null },
        goldJewelry: { bid: null, ask: null, diff: null },
        spot: { bid: null, ask: null, diff: null },
        thb: { bid: null, ask: null, diff: null },
        updatedAt: Date.now(),
        ok: false,
      };
    }
  },
);