base con autenticacion, registro, modulo encuestas
This commit is contained in:
35
apps/web/feactures/auth/components/sigin-view.tsx
Normal file
35
apps/web/feactures/auth/components/sigin-view.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
} from '@repo/shadcn/card';
|
||||
|
||||
import { cn } from '@repo/shadcn/lib/utils';
|
||||
import UserAuthForm from './user-auth-form';
|
||||
|
||||
export function LoginForm({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<'div'>) {
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||
<Card className="overflow-hidden">
|
||||
<CardContent className="grid p-0 md:grid-cols-2">
|
||||
<UserAuthForm />
|
||||
<div className="relative hidden bg-muted md:block">
|
||||
<img
|
||||
src="logo.png"
|
||||
alt="Image"
|
||||
className="absolute inset-0 p-10 h-full w-full object-cover "
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* <div className="text-balance text-center text-xs text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 hover:[&_a]:text-primary">
|
||||
By clicking continue, you agree to our <a href="#">Terms of Service</a>{" "}
|
||||
and <a href="#">Privacy Policy</a>.
|
||||
</div> */}
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
32
apps/web/feactures/auth/components/signup-view.tsx
Normal file
32
apps/web/feactures/auth/components/signup-view.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
} from '@repo/shadcn/card';
|
||||
|
||||
import { cn } from '@repo/shadcn/lib/utils';
|
||||
// import UserAuthForm from './user-auth-form';
|
||||
import UserAuthForm from './user-register-form';
|
||||
|
||||
export function LoginForm({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<'div'>) {
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||
<Card className="overflow-hidden">
|
||||
<CardContent className="grid p-0">
|
||||
<UserAuthForm />
|
||||
{/* <div className="relative hidden bg-muted md:block">
|
||||
<img
|
||||
src="logo.png"
|
||||
alt="Image"
|
||||
className="absolute inset-0 p-10 h-full w-full object-cover "
|
||||
/>
|
||||
</div> */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
139
apps/web/feactures/auth/components/user-auth-form.tsx
Normal file
139
apps/web/feactures/auth/components/user-auth-form.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
'use client';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Button } from '@repo/shadcn/button';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@repo/shadcn/form';
|
||||
import { Input } from '@repo/shadcn/input';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useState, useTransition } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
import { UserFormValue, formSchema } from '../schemas/login';
|
||||
|
||||
export default function UserAuthForm() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const callbackUrl = searchParams.get('callbackUrl');
|
||||
const [loading, startTransition] = useTransition();
|
||||
const [error, SetError] = useState<string | null>(null);
|
||||
const defaultValues = {
|
||||
username: '',
|
||||
password: '',
|
||||
};
|
||||
const form = useForm<UserFormValue>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const onSubmit = async (data: UserFormValue) => {
|
||||
SetError(null); // Limpia cualquier error previo al intentar iniciar sesión
|
||||
startTransition(async () => {
|
||||
try {
|
||||
const login = await signIn('credentials', {
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
redirect: false, // No queremos una redirección automática aquí
|
||||
});
|
||||
|
||||
|
||||
|
||||
if (login?.error) {
|
||||
const errorMessage =
|
||||
login.error === 'CredentialsSignin'
|
||||
? 'Usuario o contraseña incorrectos'
|
||||
: 'Contacte al Administrador';
|
||||
SetError(errorMessage);
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
|
||||
// Si la autenticación es exitosa y `redirect: false`, necesitamos redirigir manualmente
|
||||
if (login?.ok && !login?.error) {
|
||||
toast.success('Ingreso Exitoso!');
|
||||
router.push(callbackUrl ?? '/dashboard');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error durante el inicio de sesión:', error);
|
||||
toast.error('Hubo un error inesperado');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form {...form}>
|
||||
|
||||
<form className="p-6 md:p-8" onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<h1 className="text-2xl font-bold">Sistema Integral Fondemi</h1>
|
||||
<p className="text-balance text-muted-foreground">
|
||||
Ingresa tus datos
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Usuario</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="ingrese su usuario..."
|
||||
disabled={loading}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Contraseña</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="*************"
|
||||
disabled={loading}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
</div>
|
||||
{error && (
|
||||
<FormMessage className="text-red-500">{error}</FormMessage>
|
||||
)}{' '}
|
||||
<Button disabled={loading} type="submit" className="w-full">
|
||||
Ingresar
|
||||
</Button>
|
||||
<div className="text-center text-sm">
|
||||
No tienes una cuenta?{" "}
|
||||
<a href="/register" className="underline underline-offset-4">
|
||||
Registrate
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
321
apps/web/feactures/auth/components/user-register-form.tsx
Normal file
321
apps/web/feactures/auth/components/user-register-form.tsx
Normal file
@@ -0,0 +1,321 @@
|
||||
'use client';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Button } from '@repo/shadcn/button';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@repo/shadcn/form';
|
||||
import { Input } from '@repo/shadcn/input';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useState, useTransition } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
import { SelectSearchable } from '@repo/shadcn/select-searchable'
|
||||
|
||||
import { createUserValue, createUser } from '../schemas/register';
|
||||
import { useRegisterUser } from "../hooks/use-mutation-users";
|
||||
|
||||
import React from 'react';
|
||||
import { useStateQuery, useMunicipalityQuery, useParishQuery } from '@/feactures/location/hooks/use-query-location';
|
||||
|
||||
export default function UserAuthForm() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const callbackUrl = searchParams.get('callbackUrl');
|
||||
const [loading, startTransition] = useTransition();
|
||||
const [error, SetError] = useState<string | null>(null);
|
||||
|
||||
const [state, setState] = React.useState(0);
|
||||
const [municipality, setMunicipality] = React.useState(0);
|
||||
const [parish, setParish] = React.useState(0);
|
||||
|
||||
const [disabledMunicipality, setDisabledMunicipality] = React.useState(true);
|
||||
const [disabledParish, setDisabledParish] = React.useState(true);
|
||||
|
||||
const { data : dataState } = useStateQuery()
|
||||
const { data : dataMunicipality } = useMunicipalityQuery(state)
|
||||
const { data : dataParish } = useParishQuery(municipality)
|
||||
|
||||
const stateOptions = dataState?.data || [{id:0,name:'Sin estados'}]
|
||||
const municipalityOptions = dataMunicipality?.data || [{id:0,stateId:0,name:'Sin Municipios'}]
|
||||
const parishOptions = dataParish?.data || [{id:0,municipalityId:0,name:'Sin Parroquias'}]
|
||||
|
||||
|
||||
const defaultValues = {
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
fullname: '',
|
||||
lastname: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
role: 5
|
||||
};
|
||||
const form = useForm<createUserValue>({
|
||||
resolver: zodResolver(createUser),
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const {
|
||||
mutate: saveAccountingAccounts,
|
||||
isPending: isSaving,
|
||||
isError,
|
||||
} = useRegisterUser();
|
||||
|
||||
const onSubmit = async (data: createUserValue) => {
|
||||
SetError(null);
|
||||
|
||||
const formData = {role: 5, ...data }
|
||||
|
||||
saveAccountingAccounts(formData, {
|
||||
onSuccess: () => {
|
||||
form.reset();
|
||||
toast.success('Registro Exitoso!');
|
||||
router.push(callbackUrl ?? '/');
|
||||
},
|
||||
onError: (e) => {
|
||||
// form.setError('root', {
|
||||
// type: 'manual',
|
||||
// message: 'Error al guardar la cuenta contable',
|
||||
// });
|
||||
SetError(e.message);
|
||||
toast.error(e.message);
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form {...form}>
|
||||
|
||||
<form className="p-6 md:p-8" onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="items-center text-center">
|
||||
<h1 className="text-2xl font-bold">Sistema Integral Fondemi</h1>
|
||||
<p className="text-balance text-muted-foreground">
|
||||
Ingresa tus datos
|
||||
</p>
|
||||
{ error ? (
|
||||
<p className="text-balance text-muted-foreground">
|
||||
{error}
|
||||
</p>
|
||||
): null }
|
||||
</div>
|
||||
|
||||
<div className='grid md:grid-cols-2 gap-2'>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Usuario</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="ingrese su usuario..."
|
||||
// disabled={loading}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="fullname"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Nombre Completo</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="ingrese su Nombre..."
|
||||
// disabled={loading}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="phone"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Teléfono</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="ingrese su teléfono..."
|
||||
// disabled={loading}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Correo</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="ingrese su correo..."
|
||||
// disabled={loading}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="state"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
<FormLabel>Estado</FormLabel>
|
||||
|
||||
<SelectSearchable
|
||||
options={
|
||||
stateOptions?.map((item) => ({
|
||||
value: item.id.toString(),
|
||||
label: item.name,
|
||||
})) || []
|
||||
}
|
||||
onValueChange={(value : any) =>
|
||||
{field.onChange(Number(value)); setState(value); setDisabledMunicipality(false); setDisabledParish(true)}
|
||||
}
|
||||
placeholder="Selecciona un estado"
|
||||
defaultValue={field.value?.toString()}
|
||||
// disabled={readOnly}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="municipality"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
<FormLabel>Municipio</FormLabel>
|
||||
|
||||
<SelectSearchable
|
||||
options={
|
||||
municipalityOptions?.map((item) => ({
|
||||
value: item.id.toString(),
|
||||
label: item.name,
|
||||
})) || []
|
||||
}
|
||||
onValueChange={(value : any) =>
|
||||
{field.onChange(Number(value)); setMunicipality(value); setDisabledParish(false)}
|
||||
}
|
||||
placeholder="Selecciona un Municipio"
|
||||
defaultValue={field.value?.toString()}
|
||||
disabled={disabledMunicipality}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="parish"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
<FormLabel>Parroquia</FormLabel>
|
||||
|
||||
<SelectSearchable
|
||||
options={
|
||||
parishOptions?.map((item) => ({
|
||||
value: item.id.toString(),
|
||||
label: item.name,
|
||||
})) || []
|
||||
}
|
||||
onValueChange={(value : any) =>
|
||||
field.onChange(Number(value))
|
||||
}
|
||||
placeholder="Selecciona una Parroquia"
|
||||
defaultValue={field.value?.toString()}
|
||||
disabled={disabledParish}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Contraseña</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="*************"
|
||||
// disabled={loading}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="confirmPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Repita la contraseña</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="*************"
|
||||
// disabled={loading}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<FormMessage className="text-red-500">{error}</FormMessage>
|
||||
)}{' '}
|
||||
<Button type="submit" className="w-full">
|
||||
Registrarce
|
||||
</Button>
|
||||
<div className="text-center text-sm">
|
||||
¿Ya tienes una cuenta?{" "}
|
||||
<a href="/" className="underline underline-offset-4">Inicia Sesión</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user