Download frontend/src/components/views/AuthScreen.jsx from tamgaa/app: direct link, hf CLI and curl.
- Browser
- Download file 9.02 kB
-
https://huggingface.co/spaces/tamgaa/app/resolve/main/frontend/src/components/views/AuthScreen.jsx
- Command line
-
hf download hf://spaces/tamgaa/app/frontend/src/components/views/AuthScreen.jsx
-
curl -L -o AuthScreen.jsx https://huggingface.co/spaces/tamgaa/app/resolve/main/frontend/src/components/views/AuthScreen.jsx
9.02 kB
| import { useEffect, useRef, useState } from "react"; | |
| import { IconEye, IconEyeOff } from "../../lib/icons"; | |
| import Button from "../ui/Button"; | |
| import { ErrorState } from "../ui/States"; | |
| import Logo from "../Logo"; | |
| import AuthArt from "../AuthArt"; | |
| const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | |
| // Renders the official Google Identity Services button when a client id is set; | |
| // on success it hands the Google ID token to onCredential. Hidden otherwise. | |
| function GoogleButton({ clientId, onCredential }) { | |
| const ref = useRef(null); | |
| const cbRef = useRef(onCredential); | |
| cbRef.current = onCredential; | |
| useEffect(() => { | |
| if (!clientId) return undefined; | |
| let cancelled = false; | |
| function render() { | |
| const gid = window.google?.accounts?.id; | |
| if (cancelled || !gid || !ref.current) return; | |
| gid.initialize({ | |
| client_id: clientId, | |
| callback: (resp) => cbRef.current?.(resp.credential), | |
| }); | |
| ref.current.innerHTML = ""; | |
| gid.renderButton(ref.current, { | |
| type: "standard", | |
| theme: "outline", | |
| size: "large", | |
| text: "continue_with", | |
| width: 320, | |
| }); | |
| } | |
| if (window.google?.accounts?.id) { | |
| render(); | |
| return () => { | |
| cancelled = true; | |
| }; | |
| } | |
| let script = document.getElementById("gis-script"); | |
| if (!script) { | |
| script = document.createElement("script"); | |
| script.id = "gis-script"; | |
| script.src = "https://accounts.google.com/gsi/client"; | |
| script.async = true; | |
| document.body.appendChild(script); | |
| } | |
| script.addEventListener("load", render); | |
| return () => { | |
| cancelled = true; | |
| script.removeEventListener("load", render); | |
| }; | |
| }, [clientId]); | |
| if (!clientId) return null; | |
| return <div className="auth-google" ref={ref} />; | |
| } | |
| // Minimal, elegant auth: a quiet form panel (serif wordmark, email + password, | |
| // Google sign-in, and a demo path) beside a soft art panel. Login methods are | |
| // unchanged from before: email/password and "Continue with Google". | |
| export default function AuthScreen({ | |
| onSignIn, | |
| onSignUp, | |
| onGoogle, | |
| onDemo, | |
| googleClientId, | |
| loading = false, | |
| serverError = "", | |
| }) { | |
| const [mode, setMode] = useState("signin"); // 'signin' | 'signup' | |
| const [show, setShow] = useState(false); | |
| const [values, setValues] = useState({ email: "", password: "", confirm: "" }); | |
| const [errors, setErrors] = useState({}); | |
| const [touched, setTouched] = useState({}); | |
| const isSignup = mode === "signup"; | |
| function validate(v = values) { | |
| const e = {}; | |
| if (!v.email) e.email = "Email is required."; | |
| else if (!emailRe.test(v.email)) e.email = "Enter a valid email address."; | |
| if (!v.password) e.password = "Password is required."; | |
| else if (isSignup && v.password.length < 8) e.password = "Use at least 8 characters."; | |
| if (isSignup && v.confirm !== v.password) e.confirm = "Passwords don’t match."; | |
| return e; | |
| } | |
| function setField(name, val) { | |
| const next = { ...values, [name]: val }; | |
| setValues(next); | |
| if (touched[name]) setErrors(validate(next)); | |
| } | |
| function switchMode(next) { | |
| setMode(next); | |
| setErrors({}); | |
| setTouched({}); | |
| } | |
| async function onSubmit(ev) { | |
| ev.preventDefault(); | |
| const e = validate(); | |
| setErrors(e); | |
| setTouched({ email: true, password: true, confirm: true }); | |
| if (Object.keys(e).length) return; | |
| const payload = { email: values.email.trim(), password: values.password }; | |
| if (isSignup) await onSignUp?.(payload); | |
| else await onSignIn?.(payload); | |
| } | |
| const eid = (n) => `auth-${n}`; | |
| const errId = (n) => (errors[n] ? `${eid(n)}-err` : undefined); | |
| return ( | |
| <div className="authpage"> | |
| {/* ---- Form ---- */} | |
| <main className="authpage-form"> | |
| <div className="authpage-inner"> | |
| <div className="authpage-brand"> | |
| <Logo size={30} /> | |
| <span className="authpage-mark">Tamga</span> | |
| </div> | |
| <p className="authpage-sub"> | |
| {isSignup | |
| ? "Create your account to start auditing packets." | |
| : "Sign in to continue to your workspace."} | |
| </p> | |
| {serverError && ( | |
| <div style={{ marginBottom: 16 }}> | |
| <ErrorState>{serverError}</ErrorState> | |
| </div> | |
| )} | |
| <form className="authpage-fields" onSubmit={onSubmit} noValidate> | |
| <label className="field" htmlFor={eid("email")}> | |
| <span className="field-label">Email</span> | |
| <input | |
| id={eid("email")} | |
| className="input" | |
| type="email" | |
| inputMode="email" | |
| autoComplete="email" | |
| placeholder="you@company.com" | |
| value={values.email} | |
| aria-invalid={errors.email ? true : undefined} | |
| aria-describedby={errId("email")} | |
| onBlur={() => { | |
| setTouched((t) => ({ ...t, email: true })); | |
| setErrors(validate()); | |
| }} | |
| onChange={(e) => setField("email", e.target.value)} | |
| /> | |
| {errors.email && ( | |
| <span className="field-error" id={errId("email")} role="alert"> | |
| {errors.email} | |
| </span> | |
| )} | |
| </label> | |
| <label className="field" htmlFor={eid("password")}> | |
| <span className="field-label">Password</span> | |
| <span className="auth-input-wrap"> | |
| <input | |
| id={eid("password")} | |
| className="input" | |
| type={show ? "text" : "password"} | |
| autoComplete={isSignup ? "new-password" : "current-password"} | |
| placeholder="••••••••" | |
| value={values.password} | |
| aria-invalid={errors.password ? true : undefined} | |
| aria-describedby={errId("password")} | |
| onBlur={() => { | |
| setTouched((t) => ({ ...t, password: true })); | |
| setErrors(validate()); | |
| }} | |
| onChange={(e) => setField("password", e.target.value)} | |
| /> | |
| <button | |
| type="button" | |
| className="auth-eye" | |
| aria-pressed={show} | |
| aria-label={show ? "Hide password" : "Show password"} | |
| onClick={() => setShow((s) => !s)} | |
| > | |
| {show ? <IconEyeOff size={18} /> : <IconEye size={18} />} | |
| </button> | |
| </span> | |
| {errors.password && ( | |
| <span className="field-error" id={errId("password")} role="alert"> | |
| {errors.password} | |
| </span> | |
| )} | |
| </label> | |
| {isSignup && ( | |
| <label className="field" htmlFor={eid("confirm")}> | |
| <span className="field-label">Confirm password</span> | |
| <input | |
| id={eid("confirm")} | |
| className="input" | |
| type={show ? "text" : "password"} | |
| autoComplete="new-password" | |
| placeholder="••••••••" | |
| value={values.confirm} | |
| aria-invalid={errors.confirm ? true : undefined} | |
| aria-describedby={errId("confirm")} | |
| onBlur={() => { | |
| setTouched((t) => ({ ...t, confirm: true })); | |
| setErrors(validate()); | |
| }} | |
| onChange={(e) => setField("confirm", e.target.value)} | |
| /> | |
| {errors.confirm && ( | |
| <span className="field-error" id={errId("confirm")} role="alert"> | |
| {errors.confirm} | |
| </span> | |
| )} | |
| </label> | |
| )} | |
| <Button type="submit" variant="primary" size="lg" block loading={loading}> | |
| {isSignup ? "Create account" : "Sign in"} | |
| </Button> | |
| </form> | |
| <GoogleButton clientId={googleClientId} onCredential={onGoogle} /> | |
| <div className="auth-divider"> | |
| <span>or</span> | |
| </div> | |
| <button | |
| type="button" | |
| className="auth-demo-link" | |
| onClick={onDemo} | |
| disabled={loading} | |
| aria-busy={loading} | |
| > | |
| {loading ? "Starting demo…" : "Continue with the demo →"} | |
| </button> | |
| <p className="auth-switch"> | |
| {isSignup ? "Already have an account? " : "New to Tamga? "} | |
| <button type="button" onClick={() => switchMode(isSignup ? "signin" : "signup")}> | |
| {isSignup ? "Sign in" : "Create one"} | |
| </button> | |
| </p> | |
| </div> | |
| </main> | |
| {/* ---- Art panel ---- */} | |
| <aside className="authpage-art" aria-hidden="true"> | |
| <AuthArt /> | |
| </aside> | |
| </div> | |
| ); | |
| } | |