@aiquants/auth-react-router 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiquants/auth-react-router",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "React Router v7 / v8 auth adapter for @aiquants/auth-core: Google OAuth strategy factory (remix-auth), cookie session storage with DI test backdoor, a loader guard that authenticates at most once per request (token refresh + photo cache), auth route handlers, and a <GoogleForm> login button.",
5
5
  "sideEffects": [
6
6
  "**/*.css"
@@ -67,7 +67,7 @@
67
67
  "tsup": "^8.5.1",
68
68
  "typescript": "^5.9.3",
69
69
  "vitest": "^4.1.8",
70
- "@aiquants/auth-core": "0.3.0"
70
+ "@aiquants/auth-core": "0.5.0"
71
71
  },
72
72
  "peerDependencies": {
73
73
  "react": ">=18",
@@ -75,7 +75,7 @@
75
75
  "react-router": "^7.0.0 || ^8.0.0",
76
76
  "remix-auth": "^4.0.0",
77
77
  "remix-auth-oauth2": "^3.0.0",
78
- "@aiquants/auth-core": "^0.3.0"
78
+ "@aiquants/auth-core": "^0.5.0"
79
79
  },
80
80
  "engines": {
81
81
  "node": ">=18.0.0",
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @module admin/format
3
+ * @description Text helpers shared by the shell and the views.
4
+ * シェルとビューが共有する文言整形の補助。
5
+ *
6
+ * 数や名前を JSX で連結すると語順が英語に固定され、日付を実行環境任せで整形すると
7
+ * サーバーとブラウザで別の文字列になる (水和が壊れる)。どちらもラベル側の宣言に従わせる。
8
+ */
9
+ import type { UserAdminLabels } from "./labels"
10
+
11
+ /**
12
+ * Substitutes `{key}` placeholders in a label template.
13
+ * ラベルの雛形に含まれる `{key}` を差し替える処理。
14
+ *
15
+ * @param template Label text containing `{key}` placeholders. `{key}` を含むラベル文言。
16
+ * @param values Replacement values by key. キーごとの差し替え値。
17
+ * @returns The filled text. 差し替え済みの文言。
18
+ */
19
+ export function fill(template: string, values: Record<string, string | number>): string {
20
+ return Object.entries(values).reduce((text, [key, value]) => text.replaceAll(`{${key}}`, String(value)), template)
21
+ }
22
+
23
+ /**
24
+ * Formats an ISO timestamp with the locale the labels declare, degrading instead of throwing.
25
+ * ISO 時刻を **ラベルが宣言したロケール** で整形する処理 (例外を投げず、読める形へ退避する)。
26
+ *
27
+ * `Intl.DateTimeFormat().format()` は不正な日時で `RangeError` を投げる (`toLocaleString()` が
28
+ * 「Invalid Date」を返すのとは違う)。この関数を呼ぶのはシェルの同期バナーであり、バナーは
29
+ * **全タブで描かれる**。上流の 1 行が壊れた時刻を持っていただけで、ユーザー管理面が丸ごと
30
+ * エラー境界へ落ちる。ロケール文字列が空でも同じく投げるため、そこも退避する。
31
+ *
32
+ * オフセットの無い ISO 文字列 (`2026-03-01T09:00:00`) は仕様上 **実行環境の時間帯** で解釈される。
33
+ * サーバーとブラウザで別の時刻になり水和が壊れるため、UTC として読む。
34
+ *
35
+ * @param iso ISO 8601 timestamp. ISO 8601 の時刻 (オフセット無しは UTC として解釈)。
36
+ * @param labels Resolved label set carrying locale and time zone. ロケールと時間帯を持つラベル。
37
+ * @returns Formatted text, or the input itself when it cannot be formatted. 整形済みの文言 (整形できない場合は入力そのもの)。
38
+ */
39
+ export function formatDateTime(iso: string, labels: UserAdminLabels): string {
40
+ // オフセットが無ければ UTC を補う (実行環境の時間帯で解釈させない)
41
+ const normalized = /(?:Z|[+-]\d{2}:?\d{2})$/.test(iso) ? iso : `${iso}Z`
42
+ try {
43
+ return new Intl.DateTimeFormat(labels.locale, { timeZone: labels.timeZone, dateStyle: "medium", timeStyle: "short" }).format(new Date(normalized))
44
+ } catch {
45
+ // 不正な日時 (`RangeError: Invalid time value`) も、不正なロケール・時間帯も、ここで
46
+ // 受け止める。画面を落とすより生の値を見せる方がまだ役に立つ
47
+ return iso
48
+ }
49
+ }