Browse Source
refactor: add object support to field formatter and update internationalization translations
master
refactor: add object support to field formatter and update internationalization translations
master
3 changed files with 289 additions and 0 deletions
@ -0,0 +1,273 @@ |
|||||
|
"use client"; |
||||
|
|
||||
|
import { useEffect, useState } from "react"; |
||||
|
import { http } from "@/lib/http"; |
||||
|
import { getUserGeoRegion } from "@/lib/geo-region"; |
||||
|
import { resolveCountryName } from "@/data/countries"; |
||||
|
import { useI18n } from "@/translations/provider"; |
||||
|
|
||||
|
type TestResult = { |
||||
|
name: string; |
||||
|
url: string; |
||||
|
status: "pending" | "success" | "error"; |
||||
|
statusCode?: number; |
||||
|
data?: any; |
||||
|
error?: string; |
||||
|
timeMs?: number; |
||||
|
}; |
||||
|
|
||||
|
export default function TestApiPage() { |
||||
|
const { locale } = useI18n(); |
||||
|
const [results, setResults] = useState<Record<string, TestResult>>({}); |
||||
|
const [isRunning, setIsRunning] = useState(false); |
||||
|
const [clientInfo, setClientInfo] = useState<{ |
||||
|
userAgent: string; |
||||
|
cookie: string; |
||||
|
localStorageGeo: string | null; |
||||
|
}>({ userAgent: "", cookie: "", localStorageGeo: null }); |
||||
|
|
||||
|
const logToConsoleAndFlutter = (tag: string, payload: any) => { |
||||
|
const message = `[TEST_API] ${tag}: ${typeof payload === "string" ? payload : JSON.stringify(payload, null, 2)}`; |
||||
|
console.log(message); |
||||
|
if (typeof window !== "undefined" && (window as any).flutter_inappwebview) { |
||||
|
try { |
||||
|
(window as any).flutter_inappwebview.callHandler("log", message); |
||||
|
} catch {} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const runSingleTest = async ( |
||||
|
id: string, |
||||
|
name: string, |
||||
|
url: string, |
||||
|
fetcher: () => Promise<any>, |
||||
|
) => { |
||||
|
setResults((prev) => ({ |
||||
|
...prev, |
||||
|
[id]: { name, url, status: "pending" }, |
||||
|
})); |
||||
|
|
||||
|
const start = Date.now(); |
||||
|
try { |
||||
|
logToConsoleAndFlutter(`START ${name}`, { url }); |
||||
|
const data = await fetcher(); |
||||
|
const timeMs = Date.now() - start; |
||||
|
logToConsoleAndFlutter(`SUCCESS ${name}`, { timeMs, data }); |
||||
|
setResults((prev) => ({ |
||||
|
...prev, |
||||
|
[id]: { |
||||
|
name, |
||||
|
url, |
||||
|
status: "success", |
||||
|
statusCode: 200, |
||||
|
data, |
||||
|
timeMs, |
||||
|
}, |
||||
|
})); |
||||
|
} catch (err: any) { |
||||
|
const timeMs = Date.now() - start; |
||||
|
const status = err.response?.status || 500; |
||||
|
const errorMsg = |
||||
|
err.response?.data?.detail || |
||||
|
err.response?.data?.message || |
||||
|
err.message || |
||||
|
"Unknown error"; |
||||
|
logToConsoleAndFlutter(`ERROR ${name}`, { |
||||
|
timeMs, |
||||
|
status, |
||||
|
error: errorMsg, |
||||
|
data: err.response?.data, |
||||
|
}); |
||||
|
setResults((prev) => ({ |
||||
|
...prev, |
||||
|
[id]: { |
||||
|
name, |
||||
|
url, |
||||
|
status: "error", |
||||
|
statusCode: status, |
||||
|
error: errorMsg, |
||||
|
data: err.response?.data, |
||||
|
timeMs, |
||||
|
}, |
||||
|
})); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const runAllTests = async () => { |
||||
|
setIsRunning(true); |
||||
|
|
||||
|
if (typeof window !== "undefined") { |
||||
|
setClientInfo({ |
||||
|
userAgent: navigator.userAgent, |
||||
|
cookie: document.cookie, |
||||
|
localStorageGeo: localStorage.getItem("user_geo_region"), |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Test 1: User Region Backend API
|
||||
|
await runSingleTest( |
||||
|
"region", |
||||
|
"1. User Region API (/account/auth/user/region/)", |
||||
|
"/account/auth/user/region/", |
||||
|
async () => { |
||||
|
const res = await http.get("/account/auth/user/region/"); |
||||
|
const raw = res.data; |
||||
|
const localizedCountry = resolveCountryName( |
||||
|
raw.country || raw.country_code, |
||||
|
locale || "fa", |
||||
|
); |
||||
|
return { |
||||
|
rawResponse: raw, |
||||
|
resolvedCountryInPersian: resolveCountryName( |
||||
|
raw.country || raw.country_code, |
||||
|
"fa", |
||||
|
), |
||||
|
resolvedCountryInEnglish: resolveCountryName( |
||||
|
raw.country || raw.country_code, |
||||
|
"en", |
||||
|
), |
||||
|
currentLocaleResolved: localizedCountry, |
||||
|
}; |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
// Test 2: getUserGeoRegion helper with force refresh
|
||||
|
await runSingleTest( |
||||
|
"geo_helper", |
||||
|
"2. getUserGeoRegion(force=true)", |
||||
|
"lib/geo-region.ts", |
||||
|
async () => { |
||||
|
return await getUserGeoRegion(true); |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
// Test 3: Marriage Forms Profile Overview
|
||||
|
await runSingleTest( |
||||
|
"overview", |
||||
|
"3. Forms Profile Overview (/api/marriage/forms/profile/overview/)", |
||||
|
"/api/marriage/forms/profile/overview/?lang=fa", |
||||
|
async () => { |
||||
|
const res = await http.get( |
||||
|
"/api/marriage/forms/profile/overview/?lang=fa", |
||||
|
); |
||||
|
return { |
||||
|
completion_percent: res.data?.progress?.completion_percent, |
||||
|
sections_progress: res.data?.progress?.sections_progress, |
||||
|
sections_count: res.data?.sections?.length, |
||||
|
}; |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
// Test 4: Marriage Personal Identity Section
|
||||
|
await runSingleTest( |
||||
|
"personal_identity", |
||||
|
"4. Personal Identity Section (/sections/personal_identity/)", |
||||
|
"/api/marriage/forms/profile/sections/personal_identity/?lang=fa", |
||||
|
async () => { |
||||
|
const res = await http.get( |
||||
|
"/api/marriage/forms/profile/sections/personal_identity/?lang=fa", |
||||
|
); |
||||
|
return { |
||||
|
section_progress: res.data?.section_progress, |
||||
|
current_step: res.data?.section_progress?.current_step, |
||||
|
total_steps: res.data?.section_progress?.total_steps, |
||||
|
answers_count: Object.keys(res.data?.answers || {}).length, |
||||
|
}; |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
setIsRunning(false); |
||||
|
}; |
||||
|
|
||||
|
useEffect(() => { |
||||
|
void runAllTests(); |
||||
|
}, []); |
||||
|
|
||||
|
return ( |
||||
|
<div className="min-h-screen bg-slate-900 text-slate-100 p-4 font-sans dir-ltr text-left"> |
||||
|
<div className="max-w-3xl mx-auto space-y-4"> |
||||
|
{/* Header */} |
||||
|
<div className="bg-slate-800 rounded-2xl p-5 border border-slate-700 shadow-xl flex items-center justify-between"> |
||||
|
<div> |
||||
|
<h1 className="text-xl font-bold text-emerald-400"> |
||||
|
🧪 Marriage Backend API & GeoIP Diagnostics |
||||
|
</h1> |
||||
|
<p className="text-xs text-slate-400 mt-1"> |
||||
|
Live Phone Webview Test Runner & Logger |
||||
|
</p> |
||||
|
</div> |
||||
|
<button |
||||
|
onClick={runAllTests} |
||||
|
disabled={isRunning} |
||||
|
className="bg-emerald-600 hover:bg-emerald-500 disabled:opacity-50 text-white font-bold text-sm px-4 py-2.5 rounded-xl transition cursor-pointer" |
||||
|
> |
||||
|
{isRunning ? "Running..." : "🔄 Re-run All"} |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
{/* Client Diagnostics */} |
||||
|
<div className="bg-slate-800 rounded-2xl p-4 border border-slate-700 space-y-2 text-xs"> |
||||
|
<div className="font-bold text-slate-300">📱 Device Info:</div> |
||||
|
<div className="text-slate-400 truncate"> |
||||
|
<span className="text-slate-500">UA:</span> {clientInfo.userAgent} |
||||
|
</div> |
||||
|
<div className="text-slate-400 break-all"> |
||||
|
<span className="text-slate-500">Stored LocalStorage Geo:</span>{" "} |
||||
|
{clientInfo.localStorageGeo || "None"} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
{/* Results List */} |
||||
|
<div className="space-y-3"> |
||||
|
{Object.entries(results).map(([id, res]) => ( |
||||
|
<div |
||||
|
key={id} |
||||
|
className="bg-slate-800 rounded-2xl p-4 border border-slate-700 space-y-2 shadow-lg" |
||||
|
> |
||||
|
<div className="flex items-center justify-between"> |
||||
|
<span className="font-bold text-sm text-slate-200"> |
||||
|
{res.name} |
||||
|
</span> |
||||
|
<span |
||||
|
className={`text-xs px-2.5 py-1 rounded-full font-bold ${ |
||||
|
res.status === "success" |
||||
|
? "bg-emerald-950 text-emerald-400 border border-emerald-800" |
||||
|
: res.status === "error" |
||||
|
? "bg-rose-950 text-rose-400 border border-rose-800" |
||||
|
: "bg-amber-950 text-amber-400 border border-amber-800 animate-pulse" |
||||
|
}`}
|
||||
|
> |
||||
|
{res.status === "success" |
||||
|
? `✅ 200 OK (${res.timeMs}ms)` |
||||
|
: res.status === "error" |
||||
|
? `❌ ${res.statusCode || "Error"} (${res.timeMs}ms)` |
||||
|
: "⏳ Testing..."} |
||||
|
</span> |
||||
|
</div> |
||||
|
|
||||
|
{res.url && ( |
||||
|
<div className="font-mono text-[11px] text-slate-400 bg-slate-950 px-2.5 py-1 rounded-lg truncate"> |
||||
|
URL: {res.url} |
||||
|
</div> |
||||
|
)} |
||||
|
|
||||
|
{res.error && ( |
||||
|
<div className="p-2.5 bg-rose-950/60 border border-rose-800 rounded-xl text-rose-300 text-xs font-mono"> |
||||
|
Error: {res.error} |
||||
|
</div> |
||||
|
)} |
||||
|
|
||||
|
{res.data && ( |
||||
|
<div className="bg-slate-950 p-3 rounded-xl border border-slate-800 overflow-x-auto max-h-60 overflow-y-auto"> |
||||
|
<pre className="font-mono text-xs text-emerald-300 whitespace-pre-wrap break-all"> |
||||
|
{JSON.stringify(res.data, null, 2)} |
||||
|
</pre> |
||||
|
</div> |
||||
|
)} |
||||
|
</div> |
||||
|
))} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
import TestApiPage from "../[lang]/test-api/page"; |
||||
|
|
||||
|
export default function RootTestApiPage() { |
||||
|
return <TestApiPage />; |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue