โครงสร้างโค้ดและตัวอ่านซอร์ส
ภาพรวมทั้งโปรเจกต์: ผังไฟล์จริง เทคโนโลยีที่ใช้ และตัวอ่านซอร์สโค้ดที่ดูและดาวน์โหลดได้ทุกไฟล์
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 config02
เทคโนโลยีที่ใช้ // 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,
};
}
},
);