Download app/(app)/profile/LearningLanguagesEditor.tsx from Reubencf/PraxaLing: direct link, hf CLI and curl.
- Browser
- Download file 4.26 kB
-
https://huggingface.co/spaces/Reubencf/PraxaLing/resolve/main/app/(app)/profile/LearningLanguagesEditor.tsx
- Command line
-
hf download 'hf://spaces/Reubencf/PraxaLing/app/(app)/profile/LearningLanguagesEditor.tsx'
-
curl -L -o LearningLanguagesEditor.tsx 'https://huggingface.co/spaces/Reubencf/PraxaLing/resolve/main/app/(app)/profile/LearningLanguagesEditor.tsx'
4.26 kB
| "use client"; | |
| import { useTransition, useState } from "react"; | |
| import { useRouter } from "next/navigation"; | |
| import { languageName, LANGUAGES } from "@/lib/languages"; | |
| import { X, Plus } from "lucide-react"; | |
| import ReactCountryFlag from "react-country-flag"; | |
| export function LearningLanguagesEditor({ | |
| targetLangs, | |
| nativeLang, | |
| level, | |
| }: { | |
| targetLangs: string[]; | |
| nativeLang: string; | |
| level: string; | |
| }) { | |
| const router = useRouter(); | |
| const [isPending, startTransition] = useTransition(); | |
| const [isAdding, setIsAdding] = useState(false); | |
| function updateLangs(newList: string[]) { | |
| if (isPending) return; | |
| startTransition(async () => { | |
| try { | |
| await fetch("/api/me/profile", { | |
| method: "PUT", | |
| headers: { "content-type": "application/json" }, | |
| body: JSON.stringify({ | |
| nativeLang, | |
| targetLang: newList[0] || "en", | |
| targetLangs: newList, | |
| level | |
| }), | |
| }); | |
| router.refresh(); | |
| } finally { | |
| setIsAdding(false); | |
| } | |
| }); | |
| } | |
| const availableLangs = LANGUAGES.filter( | |
| (l) => l.code !== nativeLang && !targetLangs.includes(l.code) | |
| ); | |
| return ( | |
| <div className="space-y-4"> | |
| <div className="flex flex-wrap gap-3"> | |
| {targetLangs.map((lang) => { | |
| const label = languageName(lang); | |
| return ( | |
| <div | |
| key={lang} | |
| className="group relative flex items-center gap-2 rounded-xl border-2 border-black bg-[#FFD21E] px-4 py-2 text-sm font-black text-black shadow-[3px_3px_0px_rgba(0,0,0,1)]" | |
| > | |
| {label} | |
| <button | |
| type="button" | |
| aria-label={`Remove ${label} from learning languages`} | |
| onClick={() => updateLangs(targetLangs.filter((l) => l !== lang))} | |
| disabled={isPending || targetLangs.length <= 1} | |
| className="ml-1 p-0.5 rounded-md hover:bg-black/10 transition-colors disabled:opacity-0 disabled:pointer-events-none" | |
| > | |
| <X size={14} strokeWidth={3} /> | |
| </button> | |
| </div> | |
| ); | |
| })} | |
| <button | |
| type="button" | |
| aria-controls="learning-language-options" | |
| aria-expanded={isAdding} | |
| aria-label={isAdding ? "Close language picker" : "Add a learning language"} | |
| onClick={() => setIsAdding(!isAdding)} | |
| disabled={isPending} | |
| className="flex h-10 w-10 items-center justify-center rounded-xl border-2 border-black bg-white text-black shadow-[3px_3px_0px_rgba(0,0,0,1)] transition-all 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)] disabled:opacity-50" | |
| > | |
| <Plus size={20} strokeWidth={3} className={`transition-transform duration-200 ${isAdding ? "rotate-45" : ""}`} /> | |
| </button> | |
| </div> | |
| {isAdding && ( | |
| <div | |
| id="learning-language-options" | |
| className="max-h-64 w-full overflow-y-auto rounded-xl border-2 border-black bg-white p-1 shadow-[6px_6px_0px_rgba(0,0,0,1)]" | |
| > | |
| {availableLangs.length > 0 ? ( | |
| availableLangs.map((lang) => ( | |
| <button | |
| key={lang.code} | |
| type="button" | |
| aria-label={`Add ${lang.name} to learning languages`} | |
| onClick={() => updateLangs([...targetLangs, lang.code])} | |
| className="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-left text-sm font-bold text-black transition-colors hover:bg-[#F3F4F6]" | |
| > | |
| <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 italic text-gray-500">No more languages</div> | |
| )} | |
| </div> | |
| )} | |
| {isPending && ( | |
| <div className="text-[10px] font-black uppercase tracking-widest text-[#0EA5A4] animate-pulse"> | |
| Updating languages... | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |