webtools

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit ed6fa3546d839395f4d62508544f21ff1ef992ba
parent 6d38652a00009577fe8c467b143ce409a908cd29
Author: Minerva_juppiter <94231606+minerva-jupiter@users.noreply.github.com>
Date:   Sun, 26 Apr 2026 09:27:25 +0000

"feat(time-translate): Add time translation tool page" (#60)

success to copy to clip
Diffstat:
Mapp/page.tsx | 3+++
Aapp/time-translate/page.tsx | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackage.json | 5++++-
Mtsconfig.json | 26++++++++++++++++++++------
4 files changed, 160 insertions(+), 7 deletions(-)

diff --git a/app/page.tsx b/app/page.tsx @@ -10,6 +10,9 @@ export default function Home() { <li> <Link href="/password">Password Generator</Link> </li> + <li> + <Link href="/time-translate">Time Translate</Link> + </li> </ul> </main> ); diff --git a/app/time-translate/page.tsx b/app/time-translate/page.tsx @@ -0,0 +1,133 @@ +"use client"; +import React, { useState, useEffect, useCallback } from "react"; + +type TimeValues = { + ms: string; + iso: string; + utc: string; + jst: string; + local: string; +}; + +export default function TimeTranslate() { + const [values, setValues] = useState<TimeValues>({ + ms: "", + iso: "", + utc: "", + jst: "", + local: "", + }); + + const updateAllFromDate = useCallback( + (d: Date, excludeKey?: keyof TimeValues) => { + if (isNaN(d.getTime())) return; + + const newMs = d.getTime().toString(); + const newValues: TimeValues = { + ms: newMs, + iso: d.toISOString(), + utc: d.toUTCString(), + jst: d.toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" }), + local: d.toLocaleString(), + }; + + setValues((prev) => { + const next = { ...newValues }; + if (excludeKey) { + next[excludeKey] = prev[excludeKey]; + } + return next; + }); + }, + [], + ); + + useEffect(() => { + updateAllFromDate(new Date()); + }, [updateAllFromDate]); + + const handleChange = (key: keyof TimeValues, val: string) => { + setValues((prev) => ({ ...prev, [key]: val })); + + let d: Date | null = null; + if (key === "ms") { + d = new Date(Number.parseInt(val)); + } else if (key === "jst") { + let parseable = val; + // If it looks like a date and lacks timezone info, assume JST (+0900) + if (/^\d{4}/.test(val) && !/[Z\+\-]/.test(val)) { + parseable = `${val} GMT+0900`; + } + d = new Date(parseable); + } else { + d = new Date(val); + } + + if (d && !isNaN(d.getTime())) { + updateAllFromDate(d, key); + } + }; + + const setNow = () => { + updateAllFromDate(new Date()); + }; + + const copyToClip = async (text: string) => { + try { + await navigator.clipboard.writeText(text); + alert("success copying to clipboard"); + } catch (error) { + alert(`fail to copying to clipboard: ${error}`); + } + }; + + const fields: { label: string; key: keyof TimeValues }[] = [ + { label: "Milliseconds (Unix Timestamp ms)", key: "ms" }, + { label: "ISO 8601", key: "iso" }, + { label: "UTC", key: "utc" }, + { label: "JST (Japan Standard Time)", key: "jst" }, + { label: "Local Time", key: "local" }, + ]; + + return ( + <main> + <h2>Time Translate</h2> + <button type="button" onClick={setNow} style={{ marginBottom: 16 }}> + Set to Current Time + </button> + + {fields.map((f) => ( + <div key={f.key} style={{ marginTop: 12 }}> + <strong>{f.label}:</strong> + <div + style={{ + display: "flex", + alignItems: "center", + gap: 8, + marginTop: 4, + }} + > + <input + type="text" + value={values[f.key]} + onChange={(e) => handleChange(f.key, e.target.value)} + style={{ + flex: 1, + fontFamily: "monospace", + padding: 8, + border: "1px solid #ddd", + }} + /> + <button type="button" onClick={() => copyToClip(values[f.key])}> + Copy + </button> + </div> + </div> + ))} + + {values.ms && isNaN(new Date(Number.parseInt(values.ms)).getTime()) && ( + <div style={{ color: "red", marginTop: 12 }}>Invalid Input</div> + )} + </main> + ); +} diff --git a/package.json b/package.json @@ -22,5 +22,8 @@ "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", "@biomejs/biome": "2.4.13" - } + }, + "trustedDependencies": [ + "@sentry/cli" + ] } diff --git a/tsconfig.json b/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "ES2017", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -11,7 +15,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve", + "jsx": "react-jsx", "incremental": true, "plugins": [ { @@ -19,9 +23,19 @@ } ], "paths": { - "@/*": ["./*"] + "@/*": [ + "./*" + ] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] -} + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +}