feat: session
This commit is contained in:
+2
-2
@@ -19,7 +19,7 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@reduxjs/toolkit": "^2.0.1",
|
"@reduxjs/toolkit": "^2.0.1",
|
||||||
"appwrite": "^13.0.1",
|
"appwrite": "^13.0.1",
|
||||||
"entgamers-database": "^0.0.5",
|
"entgamers-database": "^0.0.7",
|
||||||
"entgamers-panda-preset": "0.1.1",
|
"entgamers-panda-preset": "0.1.1",
|
||||||
"formik": "^2.4.5",
|
"formik": "^2.4.5",
|
||||||
"framer-motion": "^10.17.6",
|
"framer-motion": "^10.17.6",
|
||||||
@@ -54,4 +54,4 @@
|
|||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"typescript": "*"
|
"typescript": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
'use client'
|
||||||
|
import { useAppDispatch } from '@/hooks/useAppDispatch'
|
||||||
|
import { useAppSelector } from '@/hooks/useAppSelector'
|
||||||
|
import { setSession, setStatus } from '@/state/sessionSlice'
|
||||||
|
import { AppwriteException } from 'appwrite'
|
||||||
|
import { getSession } from 'entgamers-database/frontend/session'
|
||||||
|
import { useEffect, type FC } from 'react'
|
||||||
|
|
||||||
|
const SessionConsumer: FC = () => {
|
||||||
|
const session = useAppSelector((state) => state.session)
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (session.status === 'initializing' && session.session === undefined) {
|
||||||
|
dispatch(setStatus('loading'))
|
||||||
|
getSession('current')
|
||||||
|
.then((session) => {
|
||||||
|
dispatch(setSession(session))
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (error instanceof AppwriteException) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
dispatch(setStatus('idle'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default SessionConsumer
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
'use client'
|
||||||
|
import Button from '@/components/ui/Button'
|
||||||
|
import Typography from '@/components/ui/Typography'
|
||||||
|
import FormGroup from '@/components/ui/form/FormGroup'
|
||||||
|
import Input from '@/components/ui/form/Input'
|
||||||
|
import PasswordInput from '@/components/ui/form/PasswordInput'
|
||||||
|
import { useAppDispatch } from '@/hooks/useAppDispatch'
|
||||||
|
import useSession from '@/hooks/useSession'
|
||||||
|
import { addAlert } from '@/state/feedbackSlice'
|
||||||
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
|
import { AppwriteException } from 'appwrite'
|
||||||
|
import { updateEmail } from 'entgamers-database/frontend/session'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { type FC } from 'react'
|
||||||
|
import { object, string } from 'yup'
|
||||||
|
|
||||||
|
interface UpdateEmailData {
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateEmailSchema = object({
|
||||||
|
email: string().email('El correo electrónico no es válido').required('El correo electrónico es requerido'),
|
||||||
|
password: string().required('La contraseña es requerida')
|
||||||
|
})
|
||||||
|
|
||||||
|
const UpdateEmail: FC = () => {
|
||||||
|
const { status, session } = useSession('/login')
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
const formik = useFormik<UpdateEmailData>({
|
||||||
|
initialValues: {
|
||||||
|
email: '',
|
||||||
|
password: ''
|
||||||
|
},
|
||||||
|
onSubmit: async ({ email, password }) => {
|
||||||
|
try {
|
||||||
|
await updateEmail(email, password)
|
||||||
|
dispatch(addAlert({
|
||||||
|
id: nanoid(),
|
||||||
|
title: 'Correo actualizado',
|
||||||
|
message: 'Ahora puedes iniciar sesión',
|
||||||
|
severity: 'success'
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof AppwriteException) {
|
||||||
|
dispatch(addAlert({
|
||||||
|
id: nanoid(),
|
||||||
|
message: error.message,
|
||||||
|
title: 'Error mientras se actualizaba el correo',
|
||||||
|
severity: 'error'
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
dispatch(addAlert({
|
||||||
|
id: nanoid(),
|
||||||
|
message: 'Error desconocido',
|
||||||
|
title: 'Error mientras se actualizaba el correo',
|
||||||
|
severity: 'error'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validationSchema: updateEmailSchema,
|
||||||
|
isInitialValid: false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (status !== 'idle' || session === undefined) {
|
||||||
|
// TODO: Replace with Skeleton
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Typography variant="h2">Cambia tu correo</Typography>
|
||||||
|
<form
|
||||||
|
onSubmit={formik.handleSubmit}
|
||||||
|
>
|
||||||
|
<FormGroup>
|
||||||
|
<label
|
||||||
|
htmlFor="email"
|
||||||
|
>
|
||||||
|
Correo
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
value={formik.values.email}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
status={formik.touched.email !== undefined && formik.errors.email !== undefined ? 'danger' : undefined}
|
||||||
|
/>
|
||||||
|
{formik.touched.email !== undefined && formik.errors.email !== undefined && (
|
||||||
|
<Typography variant="caption" color="danger">{formik.errors.email}</Typography>
|
||||||
|
)}
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<label
|
||||||
|
htmlFor="password"
|
||||||
|
>
|
||||||
|
Contraseña
|
||||||
|
</label>
|
||||||
|
<PasswordInput
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
value={formik.values.password}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
status={formik.touched.password !== undefined && formik.errors.password !== undefined ? 'danger' : undefined}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
{formik.touched.password !== undefined && formik.errors.password !== undefined && (
|
||||||
|
<Typography variant="caption" color="danger">{formik.errors.password}</Typography>
|
||||||
|
)}
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={formik.isSubmitting || !formik.isValid}
|
||||||
|
>
|
||||||
|
Actualizar contraseña
|
||||||
|
</Button>
|
||||||
|
</FormGroup>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UpdateEmail
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
'use client'
|
||||||
|
import Button from '@/components/ui/Button'
|
||||||
|
import Typography from '@/components/ui/Typography'
|
||||||
|
import FormGroup from '@/components/ui/form/FormGroup'
|
||||||
|
import PasswordInput from '@/components/ui/form/PasswordInput'
|
||||||
|
import { useAppDispatch } from '@/hooks/useAppDispatch'
|
||||||
|
import useSession from '@/hooks/useSession'
|
||||||
|
import { addAlert } from '@/state/feedbackSlice'
|
||||||
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
|
import { AppwriteException } from 'appwrite'
|
||||||
|
import { updatePassword } from 'entgamers-database/frontend/session'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { type FC } from 'react'
|
||||||
|
import { object, ref, string } from 'yup'
|
||||||
|
|
||||||
|
interface UpdatePasswordData {
|
||||||
|
password: string
|
||||||
|
confirmPassword: string
|
||||||
|
currentPassword: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePasswordSchema = object({
|
||||||
|
password: string()
|
||||||
|
.min(6, 'La contraseña debe tener al menos 6 caracteres')
|
||||||
|
.matches(/[a-z]/, 'La contraseña debe tener al menos una letra minúscula')
|
||||||
|
.matches(/[A-Z]/, 'La contraseña debe tener al menos una letra mayúscula')
|
||||||
|
.matches(/[0-9]/, 'La contraseña debe tener al menos un número')
|
||||||
|
.required('La contraseña es requerida'),
|
||||||
|
confirmPassword: string().oneOf([ref('password')], 'Las contraseñas no coinciden').required('La confirmación de la contraseña es requerida'),
|
||||||
|
currentPassword: string().required('La contraseña actual es requerida')
|
||||||
|
})
|
||||||
|
|
||||||
|
const UpdatePassword: FC = () => {
|
||||||
|
const { status, session } = useSession('/login')
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
const formik = useFormik<UpdatePasswordData>({
|
||||||
|
initialValues: {
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
currentPassword: ''
|
||||||
|
},
|
||||||
|
onSubmit: async ({ password, currentPassword }) => {
|
||||||
|
try {
|
||||||
|
await updatePassword(password, currentPassword)
|
||||||
|
dispatch(addAlert({
|
||||||
|
id: nanoid(),
|
||||||
|
title: 'Contrasenya actualizada',
|
||||||
|
message: 'Ahora puedes iniciar sesión',
|
||||||
|
severity: 'success'
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof AppwriteException) {
|
||||||
|
dispatch(addAlert({
|
||||||
|
id: nanoid(),
|
||||||
|
message: error.message,
|
||||||
|
title: 'Error mientras se actualizaba la contraseña',
|
||||||
|
severity: 'error'
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
dispatch(addAlert({
|
||||||
|
id: nanoid(),
|
||||||
|
message: 'Error desconocido',
|
||||||
|
title: 'Error mientras se actualizaba la contraseña',
|
||||||
|
severity: 'error'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validationSchema: updatePasswordSchema,
|
||||||
|
isInitialValid: false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (status !== 'idle' || session === undefined) {
|
||||||
|
// TODO: Replace with Skeleton
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Typography variant="h2">Actualizar contraseña</Typography>
|
||||||
|
<form
|
||||||
|
onSubmit={formik.handleSubmit}
|
||||||
|
>
|
||||||
|
<FormGroup>
|
||||||
|
<label
|
||||||
|
htmlFor="password"
|
||||||
|
>
|
||||||
|
Nueva contraseña
|
||||||
|
</label>
|
||||||
|
<PasswordInput
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
value={formik.values.password}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
status={formik.touched.password !== undefined && formik.errors.password !== undefined ? 'danger' : undefined}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
{formik.touched.password !== undefined && formik.errors.password !== undefined && (
|
||||||
|
<Typography variant="caption" color="danger">
|
||||||
|
{formik.errors.password}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<label
|
||||||
|
htmlFor="confirmPassword"
|
||||||
|
>
|
||||||
|
Confirmar nueva contraseña
|
||||||
|
</label>
|
||||||
|
<PasswordInput
|
||||||
|
id="confirmPassword"
|
||||||
|
name="confirmPassword"
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
value={formik.values.confirmPassword}
|
||||||
|
status={formik.touched.confirmPassword !== undefined && formik.errors.confirmPassword !== undefined ? 'danger' : undefined}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
{formik.touched.confirmPassword !== undefined && formik.errors.confirmPassword !== undefined && (
|
||||||
|
<Typography variant="caption" color="danger">
|
||||||
|
{formik.errors.confirmPassword}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<label
|
||||||
|
htmlFor="currentPassword"
|
||||||
|
>
|
||||||
|
Contraseña actual
|
||||||
|
</label>
|
||||||
|
<PasswordInput
|
||||||
|
id="currentPassword"
|
||||||
|
name="currentPassword"
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
value={formik.values.currentPassword}
|
||||||
|
status={formik.touched.currentPassword !== undefined && formik.errors.currentPassword !== undefined ? 'danger' : undefined}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
{formik.touched.currentPassword !== undefined && formik.errors.currentPassword !== undefined && (
|
||||||
|
<Typography variant="caption" color="danger">
|
||||||
|
{formik.errors.currentPassword}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={formik.isSubmitting || !formik.isValid}
|
||||||
|
>
|
||||||
|
Actualizar contraseña
|
||||||
|
</Button>
|
||||||
|
</FormGroup>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default UpdatePassword
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
'use client'
|
||||||
|
import Button from '@/components/ui/Button'
|
||||||
|
import Typography from '@/components/ui/Typography'
|
||||||
|
import FormGroup from '@/components/ui/form/FormGroup'
|
||||||
|
import Input from '@/components/ui/form/Input'
|
||||||
|
import { useAppDispatch } from '@/hooks/useAppDispatch'
|
||||||
|
import useSession from '@/hooks/useSession'
|
||||||
|
import { addAlert } from '@/state/feedbackSlice'
|
||||||
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
|
import { AppwriteException } from 'appwrite'
|
||||||
|
import { updateName } from 'entgamers-database/frontend/session'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { type FC } from 'react'
|
||||||
|
import { object, string } from 'yup'
|
||||||
|
|
||||||
|
interface UpdateUserNameData {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateUserNameSchema = object({
|
||||||
|
name: string().required('El nombre es requerido')
|
||||||
|
})
|
||||||
|
|
||||||
|
const UpdateUserName: FC = () => {
|
||||||
|
const { status, session } = useSession('/login')
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
const formik = useFormik<UpdateUserNameData>({
|
||||||
|
initialValues: {
|
||||||
|
name: ''
|
||||||
|
},
|
||||||
|
onSubmit: async ({ name }) => {
|
||||||
|
try {
|
||||||
|
await updateName(name)
|
||||||
|
dispatch(addAlert({
|
||||||
|
id: nanoid(),
|
||||||
|
title: 'Nombre actualizado',
|
||||||
|
message: 'Ahora puedes iniciar sesión',
|
||||||
|
severity: 'success'
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof AppwriteException) {
|
||||||
|
dispatch(addAlert({
|
||||||
|
id: nanoid(),
|
||||||
|
message: error.message,
|
||||||
|
title: 'Error mientras se actualizaba el nombre',
|
||||||
|
severity: 'error'
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
dispatch(addAlert({
|
||||||
|
id: nanoid(),
|
||||||
|
message: 'Error desconocido',
|
||||||
|
title: 'Error mientras se actualizaba el nombre',
|
||||||
|
severity: 'error'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validationSchema: UpdateUserNameSchema,
|
||||||
|
isInitialValid: false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (status !== 'idle' || session === undefined) {
|
||||||
|
// TODO: Replace with Skeleton
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Typography variant="h2">Cambia tu nombre de usuario</Typography>
|
||||||
|
<form
|
||||||
|
onSubmit={formik.handleSubmit}
|
||||||
|
>
|
||||||
|
<FormGroup>
|
||||||
|
<label
|
||||||
|
htmlFor="name"
|
||||||
|
>
|
||||||
|
Nombre de usuario
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
value={formik.values.name}
|
||||||
|
status={formik.touched.name !== undefined && formik.errors.name !== undefined ? 'danger' : undefined}
|
||||||
|
/>
|
||||||
|
{formik.touched.name !== undefined && formik.errors.name !== undefined && (
|
||||||
|
<Typography variant="caption" color="danger">{formik.errors.name}</Typography>
|
||||||
|
)}
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={formik.isSubmitting || !formik.isValid}
|
||||||
|
>
|
||||||
|
Actualizar nombre de usuario
|
||||||
|
</Button>
|
||||||
|
</FormGroup>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default UpdateUserName
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import UpdateEmail from '@/app/cuenta/UpdateEmail'
|
||||||
|
import UpdatePassword from '@/app/cuenta/UpdatePassword'
|
||||||
|
import UpdateUserName from '@/app/cuenta/UpdateUserName'
|
||||||
|
import Typography from '@/components/ui/Typography'
|
||||||
|
import { Container } from '@/styled-system/jsx'
|
||||||
|
import { type FC } from 'react'
|
||||||
|
|
||||||
|
const CuentaPage: FC = () => {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Typography variant="h1" align="center">Cuenta</Typography>
|
||||||
|
<UpdateUserName />
|
||||||
|
<UpdatePassword />
|
||||||
|
<UpdateEmail />
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default CuentaPage
|
||||||
@@ -11,6 +11,7 @@ import '@fortawesome/fontawesome-svg-core/styles.css'
|
|||||||
import { type Metadata } from 'next'
|
import { type Metadata } from 'next'
|
||||||
import { type FC, type ReactNode } from 'react'
|
import { type FC, type ReactNode } from 'react'
|
||||||
import FeedbackConsumer from './FeedbackConsumer'
|
import FeedbackConsumer from './FeedbackConsumer'
|
||||||
|
import SessionConsumer from './SessionConsumer'
|
||||||
import StateProvider from './StateProvider'
|
import StateProvider from './StateProvider'
|
||||||
|
|
||||||
config.autoAddCss = false
|
config.autoAddCss = false
|
||||||
@@ -40,6 +41,7 @@ const RootLayout: FC<RootLayoutProps> = ({ children }) => {
|
|||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
<FeedbackConsumer />
|
<FeedbackConsumer />
|
||||||
|
<SessionConsumer />
|
||||||
</StateProvider>
|
</StateProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -4,8 +4,16 @@ import Typography from '@/components/ui/Typography'
|
|||||||
import FormGroup from '@/components/ui/form/FormGroup'
|
import FormGroup from '@/components/ui/form/FormGroup'
|
||||||
import Input from '@/components/ui/form/Input'
|
import Input from '@/components/ui/form/Input'
|
||||||
import PasswordInput from '@/components/ui/form/PasswordInput'
|
import PasswordInput from '@/components/ui/form/PasswordInput'
|
||||||
|
import { useAppDispatch } from '@/hooks/useAppDispatch'
|
||||||
|
import { useAppSelector } from '@/hooks/useAppSelector'
|
||||||
|
import { addAlert } from '@/state/feedbackSlice'
|
||||||
|
import { setSession, setStatus } from '@/state/sessionSlice'
|
||||||
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
|
import { AppwriteException } from 'appwrite'
|
||||||
|
import { login } from 'entgamers-database/frontend/session'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import { type FC } from 'react'
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useEffect, type FC } from 'react'
|
||||||
import { object, string } from 'yup'
|
import { object, string } from 'yup'
|
||||||
|
|
||||||
interface LoginData {
|
interface LoginData {
|
||||||
@@ -19,16 +27,49 @@ const loginSchema = object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const LoginForm: FC = () => {
|
const LoginForm: FC = () => {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
const session = useAppSelector((state) => state.session)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const formik = useFormik<LoginData>({
|
const formik = useFormik<LoginData>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
email: '',
|
email: '',
|
||||||
password: ''
|
password: ''
|
||||||
},
|
},
|
||||||
onSubmit: (values) => {
|
onSubmit: async ({ email, password }) => {
|
||||||
console.log(values)
|
dispatch(setStatus('loading'))
|
||||||
|
try {
|
||||||
|
const session = await login(email, password)
|
||||||
|
dispatch(setSession(session))
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof AppwriteException) {
|
||||||
|
dispatch(addAlert({
|
||||||
|
id: nanoid(),
|
||||||
|
message: error.message,
|
||||||
|
title: 'Error mientras se iniciaba sesión',
|
||||||
|
severity: 'error'
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
dispatch(addAlert({
|
||||||
|
id: nanoid(),
|
||||||
|
message: 'Error desconocido',
|
||||||
|
title: 'Error mientras se iniciaba sesión',
|
||||||
|
severity: 'error'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
dispatch(setStatus('idle'))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
validationSchema: loginSchema
|
validationSchema: loginSchema
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (session.status === 'idle' && session.session !== undefined) {
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
}, [session])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const LoginPage: FC = () => {
|
|||||||
>
|
>
|
||||||
<LoginForm />
|
<LoginForm />
|
||||||
<Typography variant="caption" align="center" >
|
<Typography variant="caption" align="center" >
|
||||||
No tienes una cuenta? <NextLink href="/register">Regístrate</NextLink>
|
¿No tienes una cuenta? <NextLink href="/register">Regístrate</NextLink>
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import Typography from '@/components/ui/Typography'
|
|||||||
import FormGroup from '@/components/ui/form/FormGroup'
|
import FormGroup from '@/components/ui/form/FormGroup'
|
||||||
import Input from '@/components/ui/form/Input'
|
import Input from '@/components/ui/form/Input'
|
||||||
import PasswordInput from '@/components/ui/form/PasswordInput'
|
import PasswordInput from '@/components/ui/form/PasswordInput'
|
||||||
|
import { useAppDispatch } from '@/hooks/useAppDispatch'
|
||||||
|
import { addAlert } from '@/state/feedbackSlice'
|
||||||
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
|
import { AppwriteException } from 'appwrite'
|
||||||
|
import { register } from 'entgamers-database/frontend/session'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import { type FC } from 'react'
|
import { type FC } from 'react'
|
||||||
import { object, ref, string } from 'yup'
|
import { object, ref, string } from 'yup'
|
||||||
@@ -26,17 +31,45 @@ const RegisterSchema = object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const RegisterForm: FC = () => {
|
const RegisterForm: FC = () => {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
const formik = useFormik<RegisterData>({
|
const formik = useFormik<RegisterData>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
passwordConfirmation: ''
|
passwordConfirmation: ''
|
||||||
},
|
},
|
||||||
onSubmit: (values) => {
|
onSubmit: async ({ email, password }) => {
|
||||||
console.log(values)
|
try {
|
||||||
|
await register(email, password)
|
||||||
|
dispatch(addAlert({
|
||||||
|
id: nanoid(),
|
||||||
|
title: 'Registro completado',
|
||||||
|
message: 'Ahora puedes iniciar sesión',
|
||||||
|
severity: 'success'
|
||||||
|
}))
|
||||||
|
formik.resetForm()
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof AppwriteException) {
|
||||||
|
dispatch(addAlert({
|
||||||
|
id: nanoid(),
|
||||||
|
message: error.message,
|
||||||
|
title: 'Error mientras se registraba',
|
||||||
|
severity: 'error'
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
dispatch(addAlert({
|
||||||
|
id: nanoid(),
|
||||||
|
message: 'Error desconocido',
|
||||||
|
title: 'Error mientras se registraba',
|
||||||
|
severity: 'error'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
validationSchema: RegisterSchema
|
validationSchema: RegisterSchema
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const RegisterPage: FC = () => {
|
|||||||
>
|
>
|
||||||
<RegisterForm />
|
<RegisterForm />
|
||||||
<Typography variant="caption" align="center" >
|
<Typography variant="caption" align="center" >
|
||||||
Ya tienes una cuenta? <NextLink href="/login">Inicia sesión</NextLink>
|
¿Ya tienes una cuenta? <NextLink href="/login">Inicia sesión</NextLink>
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,16 +1,27 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import EntGamers from '@/assets/logos/EntGamers'
|
import EntGamers from '@/assets/logos/EntGamers'
|
||||||
import Menu from '@/components/layout/Menu'
|
import Menu from '@/components/layout/Menu'
|
||||||
|
import IconButton from '@/components/ui/IconButton'
|
||||||
import Tooltip from '@/components/ui/Tooltip'
|
import Tooltip from '@/components/ui/Tooltip'
|
||||||
|
import { useAppDispatch } from '@/hooks/useAppDispatch'
|
||||||
|
import { useAppSelector } from '@/hooks/useAppSelector'
|
||||||
|
import { addAlert } from '@/state/feedbackSlice'
|
||||||
|
import { setSession } from '@/state/sessionSlice'
|
||||||
import { css } from '@/styled-system/css'
|
import { css } from '@/styled-system/css'
|
||||||
import { Container } from '@/styled-system/jsx'
|
import { Container } from '@/styled-system/jsx'
|
||||||
import { iconButton } from '@/styled-system/recipes'
|
import { iconButton } from '@/styled-system/recipes'
|
||||||
import { faUser } from '@fortawesome/free-solid-svg-icons'
|
import { faRightFromBracket, faUser } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
|
import { AppwriteException } from 'appwrite'
|
||||||
|
import { logout } from 'entgamers-database/frontend/session'
|
||||||
import NextLink from 'next/link'
|
import NextLink from 'next/link'
|
||||||
import { useCallback, useEffect, useState, type FC } from 'react'
|
import { useCallback, useEffect, useState, type FC } from 'react'
|
||||||
|
|
||||||
const Header: FC = () => {
|
const Header: FC = () => {
|
||||||
|
const session = useAppSelector(state => state.session)
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
const [isScrolled, setIsScrolled] = useState(typeof window !== 'undefined' ? window.scrollY > 0 : false)
|
const [isScrolled, setIsScrolled] = useState(typeof window !== 'undefined' ? window.scrollY > 0 : false)
|
||||||
const handleScroll = useCallback(() => {
|
const handleScroll = useCallback(() => {
|
||||||
if (typeof window === 'undefined') return
|
if (typeof window === 'undefined') return
|
||||||
@@ -68,19 +79,65 @@ const Header: FC = () => {
|
|||||||
</NextLink>
|
</NextLink>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Tooltip
|
{session.status === 'idle' && typeof session.session !== 'undefined'
|
||||||
title="Próximamente"
|
? (
|
||||||
position="bottom"
|
<>
|
||||||
>
|
<Tooltip
|
||||||
<NextLink
|
title="Cuenta"
|
||||||
href="/login"
|
position="bottom"
|
||||||
className={
|
>
|
||||||
iconButton()
|
<NextLink
|
||||||
}
|
href="/cuenta"
|
||||||
>
|
className={
|
||||||
<FontAwesomeIcon icon={faUser} fixedWidth />
|
iconButton()
|
||||||
</NextLink>
|
}
|
||||||
</Tooltip>
|
>
|
||||||
|
<FontAwesomeIcon icon={faUser} fixedWidth />
|
||||||
|
</NextLink>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip
|
||||||
|
title="Cerrar sesión"
|
||||||
|
position="bottom"
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
logout('current')
|
||||||
|
.then(() => {
|
||||||
|
dispatch(setSession(undefined))
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (error instanceof AppwriteException) {
|
||||||
|
dispatch(addAlert({
|
||||||
|
id: nanoid(),
|
||||||
|
message: error.message,
|
||||||
|
title: 'Error mientras se cerraba sesión',
|
||||||
|
severity: 'error'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faRightFromBracket} fixedWidth />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<Tooltip
|
||||||
|
title="Iniciar sesión"
|
||||||
|
position="bottom"
|
||||||
|
>
|
||||||
|
<NextLink
|
||||||
|
href="/login"
|
||||||
|
className={
|
||||||
|
iconButton()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faUser} fixedWidth />
|
||||||
|
</NextLink>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
<Menu />
|
<Menu />
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { type SessionState } from '@/state/sessionSlice'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useAppSelector } from './useAppSelector'
|
||||||
|
|
||||||
|
type UseSession = (redirect?: string) => SessionState
|
||||||
|
|
||||||
|
const useSession: UseSession = (redirect?: string) => {
|
||||||
|
const { status, session } = useAppSelector((state) => state.session)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status === 'idle' && session === undefined) {
|
||||||
|
router.push(redirect ?? '/')
|
||||||
|
}
|
||||||
|
}, [status, session])
|
||||||
|
|
||||||
|
return { status, session }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSession
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { createSlice, type PayloadAction } from '@reduxjs/toolkit'
|
||||||
|
import { type Models } from 'appwrite'
|
||||||
|
|
||||||
|
export interface SessionState {
|
||||||
|
status: 'idle' | 'loading' | 'initializing'
|
||||||
|
session?: Models.Session
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: SessionState = {
|
||||||
|
status: 'initializing',
|
||||||
|
session: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionSlice = createSlice({
|
||||||
|
name: 'session',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setStatus: (state, action: PayloadAction<SessionState['status']>) => {
|
||||||
|
state.status = action.payload
|
||||||
|
},
|
||||||
|
setSession: (state, action: PayloadAction<SessionState['session']>) => {
|
||||||
|
state.session = action.payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const { setStatus, setSession } = sessionSlice.actions
|
||||||
|
|
||||||
|
export default sessionSlice
|
||||||
+3
-1
@@ -1,9 +1,11 @@
|
|||||||
import feedbackSlice from '@/state/feedbackSlice'
|
import feedbackSlice from '@/state/feedbackSlice'
|
||||||
|
import sessionSlice from '@/state/sessionSlice'
|
||||||
import { configureStore } from '@reduxjs/toolkit'
|
import { configureStore } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
feedback: feedbackSlice.reducer
|
feedback: feedbackSlice.reducer,
|
||||||
|
session: sessionSlice.reducer
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -12,8 +12,8 @@
|
|||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"module": "esnext",
|
"module": "Node16",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "Node16",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
|
|||||||
Reference in New Issue
Block a user