Browse Source

refactor: add object support to field formatter and update internationalization translations

master
mortezaei 4 days ago
parent
commit
de7c2eb953
  1. 273
      src/app/[lang]/test-api/page.tsx
  2. 11
      src/app/api/proxy/route.ts
  3. 5
      src/app/test-api/page.tsx

273
src/app/[lang]/test-api/page.tsx

@ -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>
);
}

11
src/app/api/proxy/route.ts

@ -13,6 +13,8 @@ const REQUEST_HEADERS_TO_FORWARD = [
"cookie",
"token",
"content-type",
"security-key",
"x-security-key",
"x-csrf-token",
"x-csrftoken",
"x-requested-with",
@ -144,6 +146,15 @@ function getRequestHeaders(request: NextRequest, targetUrl: URL) {
headers.set("authorization", `Token ${authKey}`);
}
// Set security-key if available in environment and not already in headers
const securityKey =
process.env.NEXT_PUBLIC_SECURITY_KEY ||
process.env.SECURITY_KEY ||
"t5yugymks5458fd4ghfg6h6";
if (securityKey && !headers.has("security-key")) {
headers.set("security-key", securityKey);
}
// Dynamically set language headers
const requestedLanguages = [
request.headers.get("x-user-language"),

5
src/app/test-api/page.tsx

@ -0,0 +1,5 @@
import TestApiPage from "../[lang]/test-api/page";
export default function RootTestApiPage() {
return <TestApiPage />;
}
Loading…
Cancel
Save