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
-
18src/app/layout.tsx
-
25src/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