Browse Source
feat: implement API proxy, add TokenSwitcher component for testing, and configure request caching headers
front-test-2
feat: implement API proxy, add TokenSwitcher component for testing, and configure request caching headers
front-test-2
6 changed files with 242 additions and 30 deletions
-
21next.config.ts
-
12src/app/api/proxy/route.ts
-
24src/app/layout.tsx
-
27src/app/request-accepted/page.tsx
-
174src/components/ui/token-switcher.tsx
-
14src/lib/http.ts
@ -0,0 +1,174 @@ |
|||||
|
"use client"; |
||||
|
|
||||
|
import { useEffect, useState } from "react"; |
||||
|
import { MdOutlineSwitchAccount } from "react-icons/md"; |
||||
|
import { setClientCookie, getClientCookie } from "@/lib/cookies"; |
||||
|
|
||||
|
export const FEMALE_TOKEN = "545f61bb3e061ccb9b19f84715eb1b1ed4740331"; |
||||
|
export const MALE_TOKEN = "f3a7543b44ef0a713d1ee0d4f7866b3825cf1308"; |
||||
|
|
||||
|
const TOKEN_COOKIE_NAME = "HABIB_TOKEN"; |
||||
|
|
||||
|
type TokenSwitcherProps = { |
||||
|
variant?: "default" | "transparent"; |
||||
|
className?: string; |
||||
|
}; |
||||
|
|
||||
|
export function TokenSwitcher({ |
||||
|
variant = "default", |
||||
|
className, |
||||
|
}: TokenSwitcherProps) { |
||||
|
const [isOpen, setIsOpen] = useState(false); |
||||
|
const [currentToken, setCurrentToken] = useState<string>(""); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (typeof window !== "undefined") { |
||||
|
const token = |
||||
|
(window as any).HABIB_TOKEN ?? |
||||
|
getClientCookie(TOKEN_COOKIE_NAME) ?? |
||||
|
getClientCookie("habib_token") ?? |
||||
|
sessionStorage.getItem(TOKEN_COOKIE_NAME) ?? |
||||
|
process.env.NEXT_PUBLIC_DEFAULT_TOKEN ?? |
||||
|
MALE_TOKEN; |
||||
|
setCurrentToken(token); |
||||
|
} |
||||
|
}, []); |
||||
|
|
||||
|
const isMale = currentToken === MALE_TOKEN; |
||||
|
const isFemale = currentToken === FEMALE_TOKEN; |
||||
|
|
||||
|
const handleSelectToken = (newToken: string) => { |
||||
|
if (typeof window !== "undefined") { |
||||
|
// Clear local storage so answers and cached state from previous user are removed
|
||||
|
try { |
||||
|
window.localStorage.clear(); |
||||
|
} catch (e) { |
||||
|
console.error("Failed to clear localStorage", e); |
||||
|
} |
||||
|
|
||||
|
setClientCookie(TOKEN_COOKIE_NAME, newToken); |
||||
|
setClientCookie("habib_token", newToken); |
||||
|
sessionStorage.setItem(TOKEN_COOKIE_NAME, newToken); |
||||
|
(window as any).HABIB_TOKEN = newToken; |
||||
|
setCurrentToken(newToken); |
||||
|
setIsOpen(false); |
||||
|
|
||||
|
// Navigate to the beginning of the flow (intro page)
|
||||
|
window.location.href = "/"; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<> |
||||
|
<button |
||||
|
type="button" |
||||
|
onClick={() => setIsOpen(true)} |
||||
|
aria-label="تغییر توکن کاربر (تست)" |
||||
|
title="تغییر توکن (آقا / خانم)" |
||||
|
className={[ |
||||
|
"inline-flex items-center gap-1.5 rounded-[15px] px-3 py-2 text-xs font-bold transition-all active:scale-95 shadow-md border border-slate-200/80 dark:border-slate-700/80 backdrop-blur-md", |
||||
|
variant === "transparent" |
||||
|
? "bg-black/40 text-white hover:bg-black/50" |
||||
|
: "bg-white/95 text-slate-800 hover:bg-white dark:bg-slate-900/95 dark:text-slate-100", |
||||
|
className, |
||||
|
] |
||||
|
.filter(Boolean) |
||||
|
.join(" ")} |
||||
|
> |
||||
|
<MdOutlineSwitchAccount className="size-5 text-rose-500" /> |
||||
|
<span>{isMale ? "آقا 👨" : isFemale ? "خانم 👩" : "توکن 🔑"}</span> |
||||
|
</button> |
||||
|
|
||||
|
{isOpen && ( |
||||
|
<div className="fixed inset-0 z-[10000] flex items-center justify-center bg-black/50 p-4 backdrop-blur-xs animate-in fade-in duration-200"> |
||||
|
<div |
||||
|
className="w-full max-w-sm rounded-2xl bg-white p-5 shadow-xl dark:bg-slate-900 border border-slate-100 dark:border-slate-800" |
||||
|
onClick={(e) => e.stopPropagation()} |
||||
|
> |
||||
|
<div className="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800"> |
||||
|
<h3 className="font-bold text-slate-900 dark:text-white text-base flex items-center gap-2"> |
||||
|
<MdOutlineSwitchAccount className="text-rose-500 text-xl" /> |
||||
|
انتخاب حساب کاربری (تست) |
||||
|
</h3> |
||||
|
<button |
||||
|
type="button" |
||||
|
onClick={() => setIsOpen(false)} |
||||
|
className="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 text-sm p-1" |
||||
|
> |
||||
|
✕ |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
<div className="mt-4 flex flex-col gap-3"> |
||||
|
{/* Male option */} |
||||
|
<button |
||||
|
type="button" |
||||
|
onClick={() => handleSelectToken(MALE_TOKEN)} |
||||
|
className={[ |
||||
|
"flex items-center justify-between p-3.5 rounded-xl border text-right transition-all dir-rtl", |
||||
|
isMale |
||||
|
? "border-rose-500 bg-rose-50/60 dark:bg-rose-950/30 text-rose-900 dark:text-rose-100 font-semibold shadow-xs" |
||||
|
: "border-slate-200 dark:border-slate-800 hover:border-slate-300 dark:hover:border-slate-700 bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200", |
||||
|
].join(" ")} |
||||
|
> |
||||
|
<div className="flex items-center gap-3"> |
||||
|
<span className="text-2xl">👨</span> |
||||
|
<div> |
||||
|
<div className="text-sm font-bold">حساب آقا</div> |
||||
|
<div className="text-[11px] font-mono text-slate-400 dark:text-slate-500 dir-ltr text-left"> |
||||
|
f3a7543b...308 |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
{isMale && ( |
||||
|
<span className="text-xs px-2.5 py-1 rounded-full bg-rose-500 text-white font-medium"> |
||||
|
فعال |
||||
|
</span> |
||||
|
)} |
||||
|
</button> |
||||
|
|
||||
|
{/* Female option */} |
||||
|
<button |
||||
|
type="button" |
||||
|
onClick={() => handleSelectToken(FEMALE_TOKEN)} |
||||
|
className={[ |
||||
|
"flex items-center justify-between p-3.5 rounded-xl border text-right transition-all dir-rtl", |
||||
|
isFemale |
||||
|
? "border-rose-500 bg-rose-50/60 dark:bg-rose-950/30 text-rose-900 dark:text-rose-100 font-semibold shadow-xs" |
||||
|
: "border-slate-200 dark:border-slate-800 hover:border-slate-300 dark:hover:border-slate-700 bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200", |
||||
|
].join(" ")} |
||||
|
> |
||||
|
<div className="flex items-center gap-3"> |
||||
|
<span className="text-2xl">👩</span> |
||||
|
<div> |
||||
|
<div className="text-sm font-bold">حساب خانم</div> |
||||
|
<div className="text-[11px] font-mono text-slate-400 dark:text-slate-500 dir-ltr text-left"> |
||||
|
545f61bb...331 |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
{isFemale && ( |
||||
|
<span className="text-xs px-2.5 py-1 rounded-full bg-rose-500 text-white font-medium"> |
||||
|
فعال |
||||
|
</span> |
||||
|
)} |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
<div className="mt-5 flex justify-end"> |
||||
|
<button |
||||
|
type="button" |
||||
|
onClick={() => setIsOpen(false)} |
||||
|
className="w-full py-2.5 rounded-xl bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300 font-medium text-sm hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors" |
||||
|
> |
||||
|
انصراف |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
)} |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export default TokenSwitcher; |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue