feat: session
This commit is contained in:
+1
-1
@@ -19,7 +19,7 @@
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@reduxjs/toolkit": "^2.0.1",
|
||||
"appwrite": "^13.0.1",
|
||||
"entgamers-database": "^0.0.5",
|
||||
"entgamers-database": "^0.0.7",
|
||||
"entgamers-panda-preset": "0.1.1",
|
||||
"formik": "^2.4.5",
|
||||
"framer-motion": "^10.17.6",
|
||||
|
||||
@@ -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 FC, type ReactNode } from 'react'
|
||||
import FeedbackConsumer from './FeedbackConsumer'
|
||||
import SessionConsumer from './SessionConsumer'
|
||||
import StateProvider from './StateProvider'
|
||||
|
||||
config.autoAddCss = false
|
||||
@@ -40,6 +41,7 @@ const RootLayout: FC<RootLayoutProps> = ({ children }) => {
|
||||
</main>
|
||||
<Footer />
|
||||
<FeedbackConsumer />
|
||||
<SessionConsumer />
|
||||
</StateProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,8 +4,16 @@ 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 { 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 { type FC } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useEffect, type FC } from 'react'
|
||||
import { object, string } from 'yup'
|
||||
|
||||
interface LoginData {
|
||||
@@ -19,16 +27,49 @@ const loginSchema = object({
|
||||
})
|
||||
|
||||
const LoginForm: FC = () => {
|
||||
const dispatch = useAppDispatch()
|
||||
const session = useAppSelector((state) => state.session)
|
||||
const router = useRouter()
|
||||
|
||||
const formik = useFormik<LoginData>({
|
||||
initialValues: {
|
||||
email: '',
|
||||
password: ''
|
||||
},
|
||||
onSubmit: (values) => {
|
||||
console.log(values)
|
||||
onSubmit: async ({ email, password }) => {
|
||||
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
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (session.status === 'idle' && session.session !== undefined) {
|
||||
router.push('/')
|
||||
}
|
||||
}, [session])
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={formik.handleSubmit}
|
||||
|
||||
@@ -42,7 +42,7 @@ const LoginPage: FC = () => {
|
||||
>
|
||||
<LoginForm />
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,11 @@ 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 { 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 { type FC } from 'react'
|
||||
import { object, ref, string } from 'yup'
|
||||
@@ -26,17 +31,45 @@ const RegisterSchema = object({
|
||||
})
|
||||
|
||||
const RegisterForm: FC = () => {
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const formik = useFormik<RegisterData>({
|
||||
initialValues: {
|
||||
email: '',
|
||||
password: '',
|
||||
passwordConfirmation: ''
|
||||
},
|
||||
onSubmit: (values) => {
|
||||
console.log(values)
|
||||
onSubmit: async ({ email, password }) => {
|
||||
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
|
||||
})
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={formik.handleSubmit}
|
||||
|
||||
@@ -43,7 +43,7 @@ const RegisterPage: FC = () => {
|
||||
>
|
||||
<RegisterForm />
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
'use client'
|
||||
import EntGamers from '@/assets/logos/EntGamers'
|
||||
import Menu from '@/components/layout/Menu'
|
||||
import IconButton from '@/components/ui/IconButton'
|
||||
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 { Container } from '@/styled-system/jsx'
|
||||
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 { nanoid } from '@reduxjs/toolkit'
|
||||
import { AppwriteException } from 'appwrite'
|
||||
import { logout } from 'entgamers-database/frontend/session'
|
||||
import NextLink from 'next/link'
|
||||
import { useCallback, useEffect, useState, type FC } from 'react'
|
||||
|
||||
const Header: FC = () => {
|
||||
const session = useAppSelector(state => state.session)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const [isScrolled, setIsScrolled] = useState(typeof window !== 'undefined' ? window.scrollY > 0 : false)
|
||||
const handleScroll = useCallback(() => {
|
||||
if (typeof window === 'undefined') return
|
||||
@@ -68,8 +79,52 @@ const Header: FC = () => {
|
||||
</NextLink>
|
||||
</div>
|
||||
<div>
|
||||
{session.status === 'idle' && typeof session.session !== 'undefined'
|
||||
? (
|
||||
<>
|
||||
<Tooltip
|
||||
title="Próximamente"
|
||||
title="Cuenta"
|
||||
position="bottom"
|
||||
>
|
||||
<NextLink
|
||||
href="/cuenta"
|
||||
className={
|
||||
iconButton()
|
||||
}
|
||||
>
|
||||
<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
|
||||
@@ -81,6 +136,8 @@ const Header: FC = () => {
|
||||
<FontAwesomeIcon icon={faUser} fixedWidth />
|
||||
</NextLink>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
<Menu />
|
||||
</div>
|
||||
</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 sessionSlice from '@/state/sessionSlice'
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
feedback: feedbackSlice.reducer
|
||||
feedback: feedbackSlice.reducer,
|
||||
session: sessionSlice.reducer
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
+2
-2
@@ -12,8 +12,8 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
Reference in New Issue
Block a user