hugging-c-ai / src /app /login /page.js
NeoPy's picture
feat: Hugging-c-ai v2 — HF OAuth + multi-provider AI + HF bucket storage + Gradio
60faa09
Raw History Blame Contribute Delete
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>
);
}