Ctrl K
ComunidadGuías…

components/login-form.tsx

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
'use client'

import { useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'

import { signIn, signInWithGoogle, signInWithOidc } from '@/lib/actions/auth'
import { safeRedirectPath } from '@/lib/auth/redirect-target'
import { useAuthCapabilities } from '@/lib/contexts/app-user-context'
import { cn } from '@/lib/utils/index'

import { Button } from '@/components/ui/button'
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle
} from '@/components/ui/card'
import { IconLogo } from '@/components/ui/icons'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'

import { PasswordInput } from './ui/password-input'

export function LoginForm({
  className,
  next,
  confirmed = false,
  ...props
}: React.ComponentPropsWithoutRef<'div'> & {
  next?: string
  confirmed?: boolean
}) {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [error, setError] = useState<string | null>(null)
  const [isLoading, setIsLoading] = useState(false)
  const router = useRouter()
  const capabilities = useAuthCapabilities()

  const handleLogin = async (e: React.FormEvent) => {
    e.preventDefault()
    setIsLoading(true)
    setError(null)

    try {
      const result = await signIn({ email, password })
      if (!result.success) throw new Error(result.error ?? 'An error occurred')
      // Redirect to the page that required sign-in (when it is a safe
      // relative path) and refresh to ensure server components get
      // updated session
      router.push(safeRedirectPath(next))
      router.refresh()
    } catch (error: unknown) {
      setError(error instanceof Error ? error.message : 'An error occurred')
    } finally {
      setIsLoading(false)
    }
  }

  const handleSocialLogin = async () => {
    setIsLoading(true)
    setError(null)

    try {
      // Supabase's redirect allowlist matches the callback URL exactly, so
      // the destination rides in a short-lived cookie, not the redirectTo.
      // Always write it: a login without a destination must clear a stale
      // cookie left by an abandoned attempt, or that old destination would
      // hijack the later sign-in.
      document.cookie = next
        ? `auth_next=${encodeURIComponent(
            safeRedirectPath(next)
          )}; path=/; max-age=300; SameSite=Lax`
        : 'auth_next=; path=/; max-age=0; SameSite=Lax'
      const result = await signInWithGoogle()
      if (!result.success)
        throw new Error(result.error ?? 'An OAuth error occurred')
      if (result.redirectTo) {
        window.location.href = result.redirectTo
      }
    } catch (error: unknown) {
      setError(
        error instanceof Error ? error.message : 'An OAuth error occurred'
      )
    } finally {
      setIsLoading(false)
    }
  }

  const handleOidcLogin = async () => {
    setIsLoading(true)
    setError(null)

    try {
      document.cookie = next
        ? `auth_next=${encodeURIComponent(
            safeRedirectPath(next)
          )}; path=/; max-age=300; SameSite=Lax`
        : 'auth_next=; path=/; max-age=0; SameSite=Lax'
      const result = await signInWithOidc()
      if (!result.success)
        throw new Error(result.error ?? 'An OAuth error occurred')
      if (result.redirectTo) {
        window.location.href = result.redirectTo
      }
    } catch (error: unknown) {
      setError(
        error instanceof Error ? error.message : 'An OAuth error occurred'
      )
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <div
      className={cn('flex flex-col items-center gap-6', className)}
      {...props}
    >
      <Card className="w-full max-w-sm">
        <CardHeader className="text-center">
          <CardTitle className="text-2xl flex flex-col items-center justify-center gap-4">
            <IconLogo className="size-12" />
            Welcome back
          </CardTitle>
          <CardDescription>
            {confirmed
              ? 'If you just confirmed your email, sign in to continue.'
              : 'Sign in to your account'}
          </CardDescription>
        </CardHeader>
        <CardContent>
          <div className="flex flex-col gap-4">
            {capabilities.oauth && (
              <>
                <Button
                  variant="outline"
                  type="button"
                  className="w-full"
                  onClick={
                    capabilities.oauthProvider
                      ? handleOidcLogin
                      : handleSocialLogin
                  }
                  disabled={isLoading}
                >
                  {capabilities.oauthProvider
                    ? `Sign In with ${capabilities.oauthProvider}`
                    : 'Sign In with Google'}
                </Button>

                <div className="relative my-2">
                  <div className="absolute inset-0 flex items-center">
                    <span className="w-full border-t" />
                  </div>
                  <div className="relative flex justify-center text-xs uppercase">
                    <span className="bg-muted px-2 text-muted-foreground">
                      Or
                    </span>
                  </div>
                </div>
              </>
            )}

            <form onSubmit={handleLogin} className="flex flex-col gap-4">
              <div className="grid gap-2">
                <Label htmlFor="email">Email</Label>
                <Input
                  id="email"
                  type="email"
                  placeholder="you@example.com"
                  required
                  value={email}
                  onChange={e => setEmail(e.target.value)}
                />
              </div>
              <div className="grid gap-2">
                <div className="flex items-center">
                  <Label htmlFor="password">Password</Label>
                  {capabilities.passwordReset && (
                    <Link
                      href="/auth/forgot-password"
                      className="ml-auto inline-block text-sm underline-offset-4 hover:underline"
                    >
                      Forgot password?
                    </Link>
                  )}
                </div>
                <PasswordInput
                  id="password"
                  type="password"
                  placeholder="********"
                  required
                  value={password}
                  onChange={e => setPassword(e.target.value)}
                />
              </div>
              {error && <p className="text-sm text-red-500">{error}</p>}
              <Button type="submit" className="w-full" disabled={isLoading}>
                {isLoading ? 'Logging in...' : 'Sign In'}
              </Button>
            </form>
          </div>
          {capabilities.signUp && (
            <div className="mt-6 text-center text-sm">
              Don&apos;t have an account?{' '}
              <Link
                href="/auth/sign-up"
                className="underline underline-offset-4"
              >
                Sign Up
              </Link>
            </div>
          )}
        </CardContent>
      </Card>
      <div className="text-center text-xs text-muted-foreground">
        <Link href="/" className="hover:underline">
          &larr; Back to Home
        </Link>
      </div>
    </div>
  )
}