option to disable password login

This commit is contained in:
Henry Dollman
2024-07-17 21:50:57 -04:00
parent 9f11c021ce
commit 8fee50d07c
10 changed files with 190 additions and 144 deletions

View File

@@ -10,12 +10,11 @@ import {
Dialog,
DialogContent,
DialogTrigger,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { useEffect, useState } from 'react'
import { AuthProviderInfo } from 'pocketbase'
import { useState } from 'react'
import { AuthMethodsList } from 'pocketbase'
import { Link } from '../router'
const honeypot = v.literal('')
@@ -57,26 +56,16 @@ const showLoginFaliedToast = () => {
export function UserAuthForm({
className,
isFirstRun,
authMethods,
...props
}: {
className?: string
isFirstRun: boolean
authMethods: AuthMethodsList
}) {
const [isLoading, setIsLoading] = useState<boolean>(false)
const [isGitHubLoading, setIsOauthLoading] = useState<boolean>(false)
const [errors, setErrors] = useState<Record<string, string | undefined>>({})
const [authProviders, setAuthProviders] = useState<AuthProviderInfo[]>([])
useEffect(() => {
pb.collection('users')
.listAuthMethods()
.then((methods) => {
console.log('methods', methods)
console.log('password active', methods.emailPassword)
setAuthProviders(methods.authProviders)
console.log('auth providers', authProviders)
})
}, [])
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
@@ -130,120 +119,133 @@ export function UserAuthForm({
}
}
if (!authMethods) {
return null
}
return (
<div className={cn('grid gap-6', className)} {...props}>
<form onSubmit={handleSubmit} onChange={() => setErrors({})}>
<div className="grid gap-2.5">
{isFirstRun && (
<div className="grid gap-1 relative">
<UserIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Label className="sr-only" htmlFor="username">
Username
</Label>
<Input
autoFocus={true}
id="username"
name="username"
required
placeholder="username"
type="username"
autoCapitalize="none"
autoComplete="username"
autoCorrect="off"
disabled={isLoading || isGitHubLoading}
className="pl-9"
/>
{errors?.username && <p className="px-1 text-xs text-red-600">{errors.username}</p>}
</div>
)}
<div className="grid gap-1 relative">
<MailIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Label className="sr-only" htmlFor="email">
Email
</Label>
<Input
id="email"
name="email"
required
placeholder={isFirstRun ? 'email' : 'name@example.com'}
type="email"
autoCapitalize="none"
autoComplete="email"
autoCorrect="off"
disabled={isLoading || isGitHubLoading}
className="pl-9"
/>
{errors?.email && <p className="px-1 text-xs text-red-600">{errors.email}</p>}
</div>
<div className="grid gap-1 relative">
<LockIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Label className="sr-only" htmlFor="pass">
Password
</Label>
<Input
id="pass"
name="password"
placeholder="password"
required
type="password"
autoComplete="current-password"
disabled={isLoading || isGitHubLoading}
className="pl-9"
/>
{errors?.password && <p className="px-1 text-xs text-red-600">{errors.password}</p>}
</div>
{isFirstRun && (
<div className="grid gap-1 relative">
<LockIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Label className="sr-only" htmlFor="pass2">
Confirm password
</Label>
<Input
id="pass2"
name="passwordConfirm"
placeholder="confirm password"
required
type="password"
autoComplete="current-password"
disabled={isLoading || isGitHubLoading}
className="pl-9"
/>
{errors?.passwordConfirm && (
<p className="px-1 text-xs text-red-600">{errors.passwordConfirm}</p>
{authMethods.emailPassword && (
<>
<form onSubmit={handleSubmit} onChange={() => setErrors({})}>
<div className="grid gap-2.5">
{isFirstRun && (
<div className="grid gap-1 relative">
<UserIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Label className="sr-only" htmlFor="username">
Username
</Label>
<Input
autoFocus={true}
id="username"
name="username"
required
placeholder="username"
type="username"
autoCapitalize="none"
autoComplete="username"
autoCorrect="off"
disabled={isLoading || isGitHubLoading}
className="pl-9"
/>
{errors?.username && (
<p className="px-1 text-xs text-red-600">{errors.username}</p>
)}
</div>
)}
<div className="grid gap-1 relative">
<MailIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Label className="sr-only" htmlFor="email">
Email
</Label>
<Input
id="email"
name="email"
required
placeholder={isFirstRun ? 'email' : 'name@example.com'}
type="email"
autoCapitalize="none"
autoComplete="email"
autoCorrect="off"
disabled={isLoading || isGitHubLoading}
className="pl-9"
/>
{errors?.email && <p className="px-1 text-xs text-red-600">{errors.email}</p>}
</div>
<div className="grid gap-1 relative">
<LockIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Label className="sr-only" htmlFor="pass">
Password
</Label>
<Input
id="pass"
name="password"
placeholder="password"
required
type="password"
autoComplete="current-password"
disabled={isLoading || isGitHubLoading}
className="pl-9"
/>
{errors?.password && <p className="px-1 text-xs text-red-600">{errors.password}</p>}
</div>
{isFirstRun && (
<div className="grid gap-1 relative">
<LockIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Label className="sr-only" htmlFor="pass2">
Confirm password
</Label>
<Input
id="pass2"
name="passwordConfirm"
placeholder="confirm password"
required
type="password"
autoComplete="current-password"
disabled={isLoading || isGitHubLoading}
className="pl-9"
/>
{errors?.passwordConfirm && (
<p className="px-1 text-xs text-red-600">{errors.passwordConfirm}</p>
)}
</div>
)}
<div className="sr-only">
{/* honeypot */}
<label htmlFor="name"></label>
<input id="name" type="text" name="name" tabIndex={-1} />
</div>
<button className={cn(buttonVariants())} disabled={isLoading}>
{isLoading ? (
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
) : (
<LogInIcon className="mr-2 h-4 w-4" />
)}
{isFirstRun ? 'Create account' : 'Sign in'}
</button>
</div>
</form>
<div className="relative">
<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-background px-2 text-muted-foreground">Or continue with</span>
</div>
)}
<div className="sr-only">
{/* honeypot */}
<label htmlFor="name"></label>
<input id="name" type="text" name="name" tabIndex={-1} />
</div>
<button className={cn(buttonVariants())} disabled={isLoading}>
{isLoading ? (
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
) : (
<LogInIcon className="mr-2 h-4 w-4" />
)}
{isFirstRun ? 'Create account' : 'Sign in'}
</button>
</div>
</form>
<div className="relative">
<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-background px-2 text-muted-foreground">Or continue with</span>
</div>
</div>
</>
)}
{authProviders.length > 0 && (
<div className="grid gap-2">
{authProviders.map((provider) => (
{authMethods.authProviders.length > 0 && (
<div className="grid gap-2 -mt-1">
{authMethods.authProviders.map((provider) => (
<button
key={provider.name}
type="button"
className={cn(buttonVariants({ variant: 'outline' }))}
className={cn(buttonVariants({ variant: 'outline' }), {
'justify-self-center': !authMethods.emailPassword,
'px-5': !authMethods.emailPassword,
})}
onClick={async () => {
setIsOauthLoading(true)
try {
@@ -275,7 +277,7 @@ export function UserAuthForm({
</div>
)}
{!authProviders.length && (
{!authMethods.authProviders.length && (
<Dialog>
<DialogTrigger asChild>
<button type="button" className={cn(buttonVariants({ variant: 'outline' }))}>
@@ -303,12 +305,15 @@ export function UserAuthForm({
</DialogContent>
</Dialog>
)}
<Link
href="/forgot-password"
className="text-sm mx-auto mt-2 hover:text-brand underline underline-offset-4 opacity-70 hover:opacity-100 transition-opacity"
>
Forgot password?
</Link>
{authMethods.emailPassword && (
<Link
href="/forgot-password"
className="text-sm mx-auto hover:text-brand underline underline-offset-4 opacity-70 hover:opacity-100 transition-opacity"
>
Forgot password?
</Link>
)}
</div>
)
}

View File

@@ -1,4 +1,4 @@
import { LoaderCircle, MailIcon, SendIcon } from 'lucide-react'
import { LoaderCircle, MailIcon, SendHorizonalIcon } from 'lucide-react'
import { Input } from '../ui/input'
import { Label } from '../ui/label'
import { useCallback, useState } from 'react'
@@ -70,7 +70,7 @@ export default function ForgotPassword() {
{isLoading ? (
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
) : (
<SendIcon className="mr-2 h-4 w-4" />
<SendHorizonalIcon className="mr-2 h-4 w-4" />
)}
Reset password
</button>
@@ -78,7 +78,7 @@ export default function ForgotPassword() {
</form>
<Dialog>
<DialogTrigger asChild>
<button className="text-sm mx-auto mt-2 hover:text-brand underline underline-offset-4 opacity-70 hover:opacity-100 transition-opacity">
<button className="text-sm mx-auto hover:text-brand underline underline-offset-4 opacity-70 hover:opacity-100 transition-opacity">
Command line instructions
</button>
</DialogTrigger>

View File

@@ -5,10 +5,12 @@ import { pb } from '@/lib/stores'
import { useStore } from '@nanostores/react'
import ForgotPassword from './forgot-pass-form'
import { $router } from '../router'
import { AuthMethodsList } from 'pocketbase'
export default function () {
const page = useStore($router)
const [isFirstRun, setFirstRun] = useState(false)
const [authMethods, setAuthMethods] = useState<AuthMethodsList>()
useEffect(() => {
document.title = 'Login / Beszel'
@@ -18,6 +20,14 @@ export default function () {
})
}, [])
useEffect(() => {
pb.collection('users')
.listAuthMethods()
.then((methods) => {
setAuthMethods(methods)
})
}, [])
const subtitle = useMemo(() => {
if (isFirstRun) {
return 'Please create an admin account'
@@ -28,9 +38,13 @@ export default function () {
}
}, [isFirstRun, page])
if (!authMethods) {
return null
}
return (
<div className="min-h-screen grid items-center py-12">
<div className="grid gap-5 w-full px-4 max-w-[22em] mx-auto">
<div className="grid gap-5 w-full px-4 mx-auto" style={{ maxWidth: '22em' }}>
<div className="text-center">
<h1 className="mb-3">
<Logo className="h-7 fill-foreground mx-auto" />
@@ -41,7 +55,7 @@ export default function () {
{page?.path === '/forgot-password' ? (
<ForgotPassword />
) : (
<UserAuthForm isFirstRun={isFirstRun} />
<UserAuthForm isFirstRun={isFirstRun} authMethods={authMethods} />
)}
</div>
</div>