added 2FA
This commit is contained in:
@@ -1,65 +0,0 @@
|
||||
|
||||
'use client'
|
||||
//https://gist.github.com/mjbalcueva/b21f39a8787e558d4c536bf68e267398
|
||||
|
||||
import { forwardRef, useState } from 'react'
|
||||
import { EyeIcon, EyeOffIcon } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input, InputProps } from '@/components/ui/input'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { FormControl } from '@/components/ui/form'
|
||||
|
||||
const PasswordInput = forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const disabled = props.value === '' || props.value === undefined ||
|
||||
props.disabled
|
||||
|
||||
return (<div className="relative">
|
||||
<Input
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
className={cn('hide-password-toggle pr-10', className)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
|
||||
onClick={() => setShowPassword((prev) => !prev)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{showPassword && !disabled ? (
|
||||
<EyeIcon
|
||||
className="h-4 w-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : (
|
||||
<EyeOffIcon
|
||||
className="h-4 w-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<span className="sr-only">
|
||||
{showPassword ? 'Hide password' : 'Show password'}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
{/* hides browsers password toggles */}
|
||||
<style>{`
|
||||
.hide-password-toggle::-ms-reveal,
|
||||
.hide-password-toggle::-ms-clear {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
PasswordInput.displayName = 'PasswordInput'
|
||||
|
||||
export { PasswordInput }
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card'
|
||||
import { Header } from '@/components/auth/header'
|
||||
import { Social } from '@/components/auth/social'
|
||||
import { BackButton } from '@/components/auth/back-button'
|
||||
import { Suspense } from 'react'
|
||||
import { Loading } from '@/components/loading'
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode
|
||||
@@ -26,31 +25,29 @@ export const CardWrapper = ({
|
||||
continueWithLabel,
|
||||
}: Props) => {
|
||||
return (
|
||||
<Suspense fallback={<Loading/>}>
|
||||
<Card
|
||||
className="border-8 border-muted shadow-2xl max-w-[430px] w-full sm:min-w-[430px]">
|
||||
<CardHeader>
|
||||
<Header label={headerLabel} title={headerTitle}/>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{children}
|
||||
</CardContent>
|
||||
{showSocial && <CardFooter className="flex-wrap">
|
||||
<div className="relative flex-none w-[100%] mb-4" style={{ background: 'block' }}>
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t"></span>
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<Card
|
||||
className="shadow-2xl max-w-[430px] w-full sm:min-w-[430px]">
|
||||
<CardHeader>
|
||||
<Header label={headerLabel} title={headerTitle}/>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{children}
|
||||
</CardContent>
|
||||
{showSocial && <CardFooter className="flex-wrap">
|
||||
<div className="relative flex-none w-[100%] mb-4" style={{ background: 'block' }}>
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t"></span>
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span
|
||||
className="bg-background px-2 text-muted-foreground">{continueWithLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Social/>
|
||||
</CardFooter>}
|
||||
<CardFooter>
|
||||
<BackButton label={backButtonLabel} href={backButtonHref}/>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Suspense>
|
||||
</div>
|
||||
<Social/>
|
||||
</CardFooter>}
|
||||
<CardFooter>
|
||||
<BackButton label={backButtonLabel} href={backButtonHref}/>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -5,14 +5,7 @@ import { useState, useTransition } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { CardWrapper } from '@/components/auth/card-wrapper'
|
||||
import { useI18n } from '@/locales/client'
|
||||
@@ -28,10 +21,9 @@ export const LoginForm = () => {
|
||||
const t = useI18n()
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const urlError = searchParams.get('error') === 'OAuthAccountNotLinked'
|
||||
? t('auth.form.error.email_in_use')
|
||||
: ''
|
||||
const urlError = searchParams.get('error') === 'OAuthAccountNotLinked' ? t('auth.form.error.email_in_use') : ''
|
||||
|
||||
const [showTwoFactor, setShowTwoFactor] = useState<boolean>(false)
|
||||
const [error, setError] = useState<string | undefined>('')
|
||||
const [success, setSuccess] = useState<string | undefined>('')
|
||||
const [isPending, startTransition] = useTransition()
|
||||
@@ -48,10 +40,26 @@ export const LoginForm = () => {
|
||||
|
||||
startTransition(() => {
|
||||
login(values).then((data) => {
|
||||
// @ts-ignore
|
||||
setError(t(data?.error))
|
||||
// @ts-ignore
|
||||
setSuccess(t(data?.success))
|
||||
//@ts-ignore
|
||||
if (data?.error) {
|
||||
form.reset() //@ts-ignore
|
||||
setError(t(data?.error))
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
if (data?.success) {
|
||||
form.reset() //@ts-ignore
|
||||
setSuccess(t(data?.success))
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
if (data?.twoFactor) { //@ts-ignore
|
||||
setShowTwoFactor(data?.twoFactor)
|
||||
}
|
||||
}).catch((err) => {
|
||||
setError('auth.common.something_went_wrong')
|
||||
//TODO: do logging
|
||||
console.log(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -67,48 +75,62 @@ export const LoginForm = () => {
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-0"
|
||||
className={showTwoFactor ? 'space-y-6' : 'space-y-2'}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<FormField control={form.control} name="email"
|
||||
render={({ field }) => (<FormItem>
|
||||
<FormLabel>{t('form.label.email')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
disabled={isPending}
|
||||
placeholder={t('form.placeholder.email')}
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage className="text-xs"/>
|
||||
</FormItem>)}/>
|
||||
{/*Password*/}
|
||||
<FormField control={form.control} name="password"
|
||||
render={({ field }) => (<FormItem>
|
||||
<FormLabel>{t('form.label.password')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
disabled={isPending}
|
||||
placeholder="******"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</FormControl>
|
||||
<Button variant="link" size="sm" asChild
|
||||
className="mt-0 p-0 items-start font-light text-sky-900">
|
||||
<Link href={AUTH_RESET_PASSWORD_URL}>{t('auth.form.login.reset_password_link_text')}</Link>
|
||||
</Button>
|
||||
<FormMessage className="text-xs"/>
|
||||
</FormItem>)}/>
|
||||
{showTwoFactor && (
|
||||
<FormField control={form.control} name="code"
|
||||
render={({ field }) => (<FormItem>
|
||||
<FormLabel>{t('form.label.two_factor')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
disabled={isPending}
|
||||
placeholder="¹₂³₄⁵₆"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage className="text-xs"/>
|
||||
</FormItem>)}/>
|
||||
)}
|
||||
{!showTwoFactor && (<>
|
||||
<FormField control={form.control} name="email"
|
||||
render={({ field }) => (<FormItem>
|
||||
<FormLabel>{t('form.label.email')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
disabled={isPending}
|
||||
placeholder={t('form.placeholder.email')}
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage className="text-xs"/>
|
||||
</FormItem>)}/>
|
||||
<FormField control={form.control} name="password"
|
||||
render={({ field }) => (<FormItem>
|
||||
<FormLabel>{t('form.label.password')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
disabled={isPending}
|
||||
placeholder="******"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</FormControl>
|
||||
<Button variant="link" size="sm" asChild
|
||||
className="mt-0 p-0 items-start font-light text-sky-900">
|
||||
<Link href={AUTH_RESET_PASSWORD_URL}>{t('auth.form.login.reset_password_link_text')}</Link>
|
||||
</Button>
|
||||
<FormMessage className="text-xs"/>
|
||||
</FormItem>)}/>
|
||||
</>)}
|
||||
</div>
|
||||
|
||||
<FormSuccess message={success}/>
|
||||
<FormError message={error || urlError}/>
|
||||
<Button type="submit" className="w-full" disabled={isPending}>
|
||||
{t('form.label.login')}
|
||||
{showTwoFactor ? t('form.button.two_factor') : t('form.button.login')}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -58,7 +58,7 @@ export const NewPasswordForm = ({ token }: { token: string }) => {
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-4"
|
||||
className="space-y-6"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<FormField control={form.control} name="password"
|
||||
|
||||
@@ -58,7 +58,7 @@ export const ResetForm = () => {
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-4"
|
||||
className="space-y-6"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<FormField control={form.control} name="email"
|
||||
|
||||
Reference in New Issue
Block a user