Download src/app/login/page.js from NeoPy/hugging-c-ai: direct link, hf CLI and curl.
- Browser
- Download file 7.81 kB
-
https://huggingface.co/NeoPy/hugging-c-ai/resolve/main/src/app/login/page.js
- Command line
-
hf download hf://NeoPy/hugging-c-ai/src/app/login/page.js
-
curl -L -o page.js https://huggingface.co/NeoPy/hugging-c-ai/resolve/main/src/app/login/page.js
7.81 kB
| "use client"; | |
| import { signIn, useSession } from "next-auth/react"; | |
| import { useEffect, useState, Suspense } from "react"; | |
| import { useRouter, useSearchParams } from "next/navigation"; | |
| import { FaKey, FaInfoCircle, FaArrowRight } from "react-icons/fa"; | |
| import toast, { Toaster } from "react-hot-toast"; | |
| function HuggingFaceLogo({ className = "w-4 h-4" }) { | |
| return ( | |
| <svg className={className} viewBox="0 0 120 120" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> | |
| <path d="M60 0C26.86 0 0 26.86 0 60s26.86 60 60 60 60-26.86 60-60S93.14 0 60 0z" /> | |
| <g fill="#fff"> | |
| <circle cx="42" cy="50" r="6" /> | |
| <circle cx="78" cy="50" r="6" /> | |
| <path d="M30 70c5 12 17 20 30 20s25-8 30-20" stroke="#fff" strokeWidth="6" fill="none" strokeLinecap="round" /> | |
| </g> | |
| </svg> | |
| ); | |
| } | |
| function LoginContent() { | |
| const { data: session, status } = useSession(); | |
| const router = useRouter(); | |
| const searchParams = useSearchParams(); | |
| const next = searchParams.get("callbackUrl") || searchParams.get("next") || "/"; | |
| const [activeTab, setActiveTab] = useState("huggingface"); | |
| const [apiKeyInput, setApiKeyInput] = useState(""); | |
| const [isSubmitting, setIsSubmitting] = useState(false); | |
| useEffect(() => { | |
| if (status === "authenticated") router.push(next); | |
| }, [status, router, next]); | |
| const handleApiKeyLogin = async (e) => { | |
| e.preventDefault(); | |
| const key = apiKeyInput.trim(); | |
| if (!key) { toast.error("Please enter a valid API key or HF token"); return; } | |
| if (key.length < 5) { toast.error("Key appears too short"); return; } | |
| setIsSubmitting(true); | |
| try { | |
| const res = await signIn("credentials", { apiKey: key, redirect: false, callbackUrl: next }); | |
| if (res?.error) toast.error(res.error || "Failed to sign in"); | |
| else { toast.success("Signed in successfully!"); router.push(next); } | |
| } catch (err) { | |
| console.error(err); | |
| toast.error("An error occurred during authentication"); | |
| } finally { | |
| setIsSubmitting(false); | |
| } | |
| }; | |
| return ( | |
| <div className="min-h-dvh flex items-center justify-center bg-bg-page px-6 text-primary-text select-none"> | |
| <Toaster position="top-right" /> | |
| <div className="relative bg-bg-card border border-divider w-full max-w-md rounded-xl p-8 space-y-6 shadow-2xl animate-scale-up"> | |
| <div className="flex flex-col items-center text-center space-y-3"> | |
| <div className="w-14 h-14 rounded-full bg-primary/10 flex items-center justify-center text-2xl text-primary font-black shadow-md shadow-primary/15">🤗</div> | |
| <h2 className="text-2xl font-black uppercase tracking-tight">Sign In to Hugging-c-ai</h2> | |
| <p className="text-xs font-semibold text-secondary-text leading-relaxed px-2"> | |
| Choose your sign-in method: HuggingFace OAuth (recommended) or a custom API key / HF token. | |
| </p> | |
| </div> | |
| <div className="flex bg-bg-page p-1 rounded-lg border border-divider/60"> | |
| <button type="button" onClick={() => setActiveTab("huggingface")} | |
| className={`flex-1 py-2 rounded-md text-xs font-bold transition-all flex items-center justify-center gap-2 ${ | |
| activeTab === "huggingface" ? "bg-bg-card text-white shadow-sm border border-divider/40" : "text-secondary-text hover:text-white" | |
| }`}> | |
| <HuggingFaceLogo className="w-3.5 h-3.5 text-yellow-400" /> | |
| <span>HuggingFace</span> | |
| </button> | |
| <button type="button" onClick={() => setActiveTab("apikey")} | |
| className={`flex-1 py-2 rounded-md text-xs font-bold transition-all flex items-center justify-center gap-2 ${ | |
| activeTab === "apikey" ? "bg-bg-card text-white shadow-sm border border-divider/40" : "text-secondary-text hover:text-white" | |
| }`}> | |
| <FaKey className="text-amber-400" /> | |
| <span>Use API Key</span> | |
| </button> | |
| </div> | |
| {activeTab === "huggingface" ? ( | |
| <div className="space-y-4 pt-2"> | |
| <button onClick={() => signIn("huggingface", { callbackUrl: next })} | |
| className="w-full py-3.5 bg-yellow-400 text-neutral-950 rounded-full text-xs font-bold flex items-center justify-center gap-3 hover:bg-yellow-300 transition-all shadow-md active:scale-[0.98] cursor-pointer"> | |
| <HuggingFaceLogo className="w-4 h-4 text-neutral-950" /> | |
| <span>Continue with HuggingFace</span> | |
| </button> | |
| <div className="text-[11px] text-secondary-text space-y-1.5 text-center"> | |
| <p><span className="text-emerald-400 font-semibold">✓ Free</span> — get 50 starter credits</p> | |
| <p><span className="text-emerald-400 font-semibold">✓ HF Bucket</span> — uploads save to your own HF dataset repo</p> | |
| <p><span className="text-emerald-400 font-semibold">✓ Gradio</span> — call your own HF Spaces as LLM endpoints</p> | |
| </div> | |
| <p className="text-[10px] text-center text-secondary-text/70"> | |
| We request scopes: <code>openid</code>, <code>profile</code>, <code>email</code>, <code>repo:write</code>, <code>read</code>. Your token is stored securely. | |
| </p> | |
| </div> | |
| ) : ( | |
| <form onSubmit={handleApiKeyLogin} className="space-y-4 pt-2"> | |
| <div className="space-y-1.5"> | |
| <label className="block text-[11px] uppercase font-bold text-secondary-text tracking-wider">API Key / HF Token</label> | |
| <div className="relative"> | |
| <input type="password" value={apiKeyInput} onChange={(e) => setApiKeyInput(e.target.value)} | |
| placeholder="hf_... | sk-... | sk-ant... | gsk_... | mu_..." | |
| className="w-full bg-bg-page border border-divider rounded-lg px-3.5 py-2.5 text-xs text-white placeholder-secondary-text/50 focus:outline-none focus:border-primary transition-colors" /> | |
| </div> | |
| <div className="flex justify-between items-center"> | |
| <span className="text-[10px] text-secondary-text">Auto-detects: HF · OpenAI · Anthropic · Groq · MuAPI</span> | |
| <a href="https://huggingface.co/settings/tokens" target="_blank" rel="noopener noreferrer" className="text-[10px] text-primary hover:underline font-semibold">Get HF Token →</a> | |
| </div> | |
| </div> | |
| <button type="submit" disabled={isSubmitting || !apiKeyInput.trim()} | |
| className="w-full py-3 bg-primary hover:bg-primary/90 text-white rounded-full text-xs font-bold flex items-center justify-center gap-2 transition-all shadow-md active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer"> | |
| <span>{isSubmitting ? "Authenticating..." : "Sign In with Key"}</span> | |
| <FaArrowRight className="text-xs" /> | |
| </button> | |
| <p className="text-[11px] text-center text-amber-400/90 font-medium">âš¡ Use your own provider key. 0 website credits required.</p> | |
| </form> | |
| )} | |
| <div className="flex items-start gap-2.5 bg-primary/5 border border-primary/10 p-3.5 rounded text-[11px] leading-relaxed text-secondary-text"> | |
| <FaInfoCircle className="text-primary text-xs shrink-0 mt-0.5" /> | |
| <span>By signing in, you agree to our Terms. Tokens are encrypted at rest and never shared.</span> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| export default function Login() { | |
| return ( | |
| <Suspense fallback={ | |
| <div className="min-h-dvh flex items-center justify-center bg-bg-page text-primary-text"> | |
| <div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" /> | |
| </div> | |
| }> | |
| <LoginContent /> | |
| </Suspense> | |
| ); | |
| } | |