first commit
This commit is contained in:
commit
efa5080897
127 changed files with 18610 additions and 0 deletions
89
frontend/src/features/auth/AuthPage.tsx
Normal file
89
frontend/src/features/auth/AuthPage.tsx
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import { ArrowLeft, ArrowRight, Check, Eye, EyeOff, LockKeyhole, Mail, User } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import type { FormEvent } from 'react'
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { authApi } from '../../lib/api'
|
||||
import { useAuth } from './auth-context'
|
||||
|
||||
export function AuthPage({ initialMode }: { initialMode: 'login' | 'register' }) {
|
||||
const [mode, setMode] = useState(initialMode)
|
||||
const [name, setName] = useState('')
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const { setUser } = useAuth()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
|
||||
async function submit(event: FormEvent) {
|
||||
event.preventDefault()
|
||||
setError('')
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const user = mode === 'login'
|
||||
? await authApi.login(email, password)
|
||||
: await authApi.register(name, email, password)
|
||||
setUser(user)
|
||||
const destination = (location.state as { from?: string } | null)?.from ?? '/console'
|
||||
navigate(destination, { replace: true })
|
||||
} catch (reason) {
|
||||
setError(reason instanceof Error ? reason.message : 'Unable to continue')
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
function switchMode(nextMode: 'login' | 'register') {
|
||||
setMode(nextMode)
|
||||
setError('')
|
||||
navigate(nextMode === 'login' ? '/login' : '/register', { replace: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="auth-page">
|
||||
<div className="auth-visual">
|
||||
<Link to="/" className="auth-brand"><img src="/urmi-logo.png" alt="Urmi" /></Link>
|
||||
<div className="auth-message">
|
||||
<span>SMS FOR AI AGENTS</span>
|
||||
<h1>From agent decision to human action.</h1>
|
||||
<p>Secure messaging infrastructure with delivery, compliance, and observability built in.</p>
|
||||
<div><span><Check size={15} /> Unified agent messaging API</span><span><Check size={15} /> India-ready compliance</span><span><Check size={15} /> Complete event visibility</span></div>
|
||||
</div>
|
||||
<small>Build → Test → Deploy → Observe</small>
|
||||
</div>
|
||||
|
||||
<main className="auth-main">
|
||||
<Link to="/" className="back-home"><ArrowLeft size={15} /> Back to Urmi</Link>
|
||||
<section className="auth-card">
|
||||
<div className="auth-tabs">
|
||||
<button className={mode === 'login' ? 'active' : ''} onClick={() => switchMode('login')}>Log in</button>
|
||||
<button className={mode === 'register' ? 'active' : ''} onClick={() => switchMode('register')}>Create account</button>
|
||||
</div>
|
||||
<div className="auth-heading">
|
||||
<h2>{mode === 'login' ? 'Welcome back' : 'Start building with Urmi'}</h2>
|
||||
<p>{mode === 'login' ? 'Access your agent messaging workspace.' : 'Create your workspace and send your first test message.'}</p>
|
||||
</div>
|
||||
|
||||
<a className="google-button" href={authApi.googleUrl}>
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#4285F4" d="M21.6 12.23c0-.71-.06-1.39-.18-2.05H12v3.87h5.38a4.6 4.6 0 0 1-2 3.02v2.51h3.24c1.9-1.75 2.98-4.33 2.98-7.35Z"/><path fill="#34A853" d="M12 22c2.7 0 4.98-.9 6.63-2.42l-3.24-2.51c-.9.6-2.05.96-3.39.96-2.61 0-4.82-1.76-5.61-4.13H3.04v2.59A10 10 0 0 0 12 22Z"/><path fill="#FBBC05" d="M6.39 13.9A6.01 6.01 0 0 1 6.08 12c0-.66.11-1.3.31-1.9V7.51H3.04A10 10 0 0 0 2 12c0 1.61.38 3.14 1.04 4.49l3.35-2.59Z"/><path fill="#EA4335" d="M12 5.97c1.47 0 2.79.51 3.83 1.5l2.87-2.88A9.64 9.64 0 0 0 12 2a10 10 0 0 0-8.96 5.51l3.35 2.59C7.18 7.73 9.39 5.97 12 5.97Z"/></svg>
|
||||
Continue with Google
|
||||
</a>
|
||||
|
||||
<div className="auth-divider"><span>or continue with email</span></div>
|
||||
|
||||
<form onSubmit={submit}>
|
||||
{mode === 'register' && <label><span>Name</span><div><User size={17} /><input required minLength={2} autoComplete="name" value={name} onChange={(e) => setName(e.target.value)} placeholder="Your name" /></div></label>}
|
||||
<label><span>Email address</span><div><Mail size={17} /><input required type="email" autoComplete="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@company.com" /></div></label>
|
||||
<label><span>Password</span><div><LockKeyhole size={17} /><input required minLength={mode === 'register' ? 10 : 1} type={showPassword ? 'text' : 'password'} autoComplete={mode === 'login' ? 'current-password' : 'new-password'} value={password} onChange={(e) => setPassword(e.target.value)} placeholder={mode === 'register' ? 'At least 10 characters' : 'Your password'} /><button type="button" aria-label="Toggle password visibility" onClick={() => setShowPassword(!showPassword)}>{showPassword ? <EyeOff size={17} /> : <Eye size={17} />}</button></div></label>
|
||||
{mode === 'login' && <div className="forgot-row"><span>Secure, encrypted sign in</span><button type="button">Forgot password?</button></div>}
|
||||
{error && <p className="auth-error">{error}</p>}
|
||||
<button className="auth-submit" disabled={submitting}>{submitting ? 'Please wait…' : mode === 'login' ? 'Log in to Urmi' : 'Create my account'} {!submitting && <ArrowRight size={17} />}</button>
|
||||
</form>
|
||||
<p className="auth-terms">By continuing, you agree to Urmi’s Terms of Service and Privacy Policy.</p>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue