PraxaLing / components /LanguageSwitcher.tsx
Reubencf's picture
Deploy: PraxaLing rename + HF Docker Space
16fde87
"use client";
import { useTransition, useState } from "react";
import { useRouter } from "next/navigation";
import ReactCountryFlag from "react-country-flag";
import { LANGUAGES } from "@/lib/languages";
import { Plus } from "lucide-react";
export function LanguageSwitcher({
targetLangs,
currentLang,
nativeLang,
level,
}: {
targetLangs: string[];
currentLang: string;
nativeLang: string;
level: string;
}) {
const router = useRouter();
const [isPending, startTransition] = useTransition();
const [pendingLang, setPendingLang] = useState<string | null>(null);
const [isAdding, setIsAdding] = useState(false);
function updateProfile(newTargetLangs: string[], switchTo: string) {
if (isPending) return;
setPendingLang(switchTo);
startTransition(async () => {
try {
await fetch("/api/me/profile", {
method: "PUT",
headers: { "content-type": "application/json" },
body: JSON.stringify({ nativeLang, targetLang: switchTo, targetLangs: newTargetLangs, level }),
});
router.refresh();
} finally {
setPendingLang(null);
setIsAdding(false);
}
});
}
const availableLangs = LANGUAGES.filter((l) => l.code !== nativeLang && !targetLangs.includes(l.code));
return (
<div className="flex flex-wrap items-center gap-2.5 mt-4">
{targetLangs.map((lang) => {
const language = LANGUAGES.find((l) => l.code === lang);
if (!language) return null;
const isActive = lang === (pendingLang ?? currentLang);
const isCurrentlySwitching = pendingLang === lang;
return (
<button
key={lang}
onClick={() => updateProfile(targetLangs, lang)}
disabled={isPending}
className={`group relative flex items-center gap-2 px-3.5 py-1.5 rounded-xl border-2 border-black text-sm font-black transition-all duration-200 ${
isActive
? "bg-[#FFD21E] text-black shadow-[2px_2px_0px_rgba(0,0,0,1)] translate-x-[1px] translate-y-[1px]"
: "bg-white text-black shadow-[3px_3px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[5px_5px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-[1px_1px_0px_rgba(0,0,0,1)]"
} ${isPending && !isCurrentlySwitching ? "pointer-events-none" : ""}`}
>
{isCurrentlySwitching ? (
<div className="h-5 w-5 animate-spin rounded-full border-2 border-black border-t-transparent" />
) : (
<ReactCountryFlag
countryCode={language.flag}
svg
style={{ width: "1.25rem", height: "1.25rem" }}
className="transition-transform duration-200 group-hover:scale-110"
/>
)}
<span>{language.name}</span>
</button>
);
})}
<div className="relative">
<button
onClick={() => setIsAdding(!isAdding)}
disabled={isPending}
className="flex items-center justify-center h-9 w-9 rounded-xl border-2 border-black bg-white text-black shadow-[3px_3px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[5px_5px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-[1px_1px_0px_rgba(0,0,0,1)] transition-all disabled:opacity-50"
>
<Plus size={18} strokeWidth={2.5} className={`transition-transform duration-200 ${isAdding ? "rotate-45" : ""}`} />
</button>
{isAdding && (
<div className="absolute top-full left-0 mt-2 w-48 max-h-64 overflow-y-auto rounded-xl border-2 border-black bg-white shadow-[4px_4px_0px_rgba(0,0,0,1)] z-50 p-1">
{availableLangs.length > 0 ? (
availableLangs.map((lang) => (
<button
key={lang.code}
onClick={() => updateProfile([...targetLangs, lang.code], lang.code)}
className="flex items-center gap-3 w-full px-3 py-2 text-sm font-bold text-black hover:bg-[#F3F4F6] rounded-lg transition-colors text-left"
>
<ReactCountryFlag countryCode={lang.flag} svg style={{ width: "1.1rem", height: "1.1rem" }} />
<span>{lang.name}</span>
</button>
))
) : (
<div className="px-3 py-2 text-xs font-bold text-gray-500 italic">No more languages</div>
)}
</div>
)}
</div>
</div>
);
}