LoginPage.tsx 3.56 KB
import { useEffect, useState, type FormEvent } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import { useAuth } from '@/auth/AuthContext'
import { meta } from '@/api/client'
import type { MetaInfo } from '@/types/api'
import { ErrorBox } from '@/components/ErrorBox'

interface LocationState {
  from?: string
}

export function LoginPage() {
  const { login, token, loading } = useAuth()
  const navigate = useNavigate()
  const location = useLocation()
  const [username, setUsername] = useState('admin')
  const [password, setPassword] = useState('')
  const [error, setError] = useState<Error | null>(null)
  const [info, setInfo] = useState<MetaInfo | null>(null)

  useEffect(() => {
    meta.info().then(setInfo).catch(() => setInfo(null))
  }, [])

  // If already logged in (e.g. nav back to /login), bounce to dashboard.
  useEffect(() => {
    if (token) {
      const dest = (location.state as LocationState | null)?.from ?? '/'
      navigate(dest, { replace: true })
    }
  }, [token, navigate, location.state])

  const onSubmit = async (e: FormEvent) => {
    e.preventDefault()
    setError(null)
    try {
      await login(username, password)
      // Effect above handles the redirect; nothing else to do here.
    } catch (err: unknown) {
      setError(err instanceof Error ? err : new Error(String(err)))
    }
  }

  return (
    <div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-slate-100 to-brand-50 p-6">
      <div className="w-full max-w-md">
        <div className="mb-6 text-center">
          <h1 className="text-3xl font-bold text-brand-600">vibe_erp</h1>
          <p className="mt-1 text-sm text-slate-500">
            Composable ERP framework for the printing industry
          </p>
        </div>
        <div className="card p-6">
          <form onSubmit={onSubmit} className="space-y-4">
            <div>
              <label className="block text-sm font-medium text-slate-700">Username</label>
              <input
                type="text"
                value={username}
                onChange={(e) => setUsername(e.target.value)}
                className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-brand-500 focus:ring-brand-500"
                required
                autoFocus
              />
            </div>
            <div>
              <label className="block text-sm font-medium text-slate-700">Password</label>
              <input
                type="password"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-brand-500 focus:ring-brand-500"
                required
              />
              <p className="mt-1 text-xs text-slate-400">
                The bootstrap admin password is printed to the application boot log on first start.
              </p>
            </div>
            {error ? <ErrorBox error={error} /> : null}
            <button type="submit" className="btn-primary w-full" disabled={loading}>
              {loading ? 'Signing in…' : 'Sign in'}
            </button>
          </form>
        </div>
        <p className="mt-4 text-center text-xs text-slate-400">
          {info ? (
            <>
              Connected to <span className="font-mono">{info.name}</span>{' '}
              <span className="font-mono">{info.implementationVersion}</span>
            </>
          ) : (
            'Connecting…'
          )}
        </p>
      </div>
    </div>
  )
}