diff --git a/bun.lockb b/bun.lockb index 7e8b302..bdc0a10 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 69f4d9d..a5221bd 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,15 @@ "scripts": { "develop": "next dev", "build": "next build", - "prestart": "yarn install && next build", + "prestart": "bun install && next build", "start": "next start", "lint": "next lint", - "prepare": "panda codegen && husky install" + "prepare": "panda codegen && husky install", + "prisma:generate": "prisma generate --schema=./node_modules/entgamers-database/prisma/schema.prisma", + "prisma:db:push": "prisma db push --schema=./node_modules/entgamers-database/prisma/schema.prisma", + "prisma:migrate:reset": "prisma migrate reset --schema=./node_modules/entgamers-database/prisma/schema.prisma", + "prisma:studio": "prisma studio --schema=./node_modules/entgamers-database/prisma/schema.prisma", + "postinstall": "bun prisma:generate" }, "dependencies": { "@fontsource/open-sans": "^5.0.20", @@ -19,8 +24,8 @@ "@fortawesome/react-fontawesome": "^0.2.0", "@reduxjs/toolkit": "^2.0.1", "appwrite": "^13.0.1", - "entgamers-database": "^0.0.7", - "entgamers-panda-preset": "0.1.1", + "entgamers-database": "0.0.13", + "entgamers-panda-preset": "0.1.2", "formik": "^2.4.5", "framer-motion": "^10.17.6", "isomorphic-fetch": "^3.0.0", diff --git a/src/app/api/teamAplications/[id]/route.ts b/src/app/api/teamAplications/[id]/route.ts new file mode 100644 index 0000000..b0ce2f7 --- /dev/null +++ b/src/app/api/teamAplications/[id]/route.ts @@ -0,0 +1,44 @@ +import { handleError } from '@/utilities/apiRoutes' +import { teamApplicationDataSchema, teamApplicationParamsSchema, type TeamApplicationRouteData } from '@/utilities/teamApplication' +import { deleteTeamApplication, getTeamApplication, updateTeamApplication } from 'entgamers-database/backend/teamApplication' +import { NextResponse } from 'next/server' + +export const GET = async (Request: Request, { params }: TeamApplicationRouteData): Promise => { + try { + const { id } = await teamApplicationParamsSchema.validate(params) + const teamApplication = await getTeamApplication({ where: { id } }) + if (teamApplication === null) { + return new NextResponse(null, { status: 404 }) + } + return NextResponse.json(teamApplication, { status: 200 }) + } catch (error) { + return handleError(error) + } +} + +export const POST = async (Request: Request, { params }: TeamApplicationRouteData): Promise => { + try { + const body: unknown = await Request.json() + const { id } = await teamApplicationParamsSchema.validate(params) + const teamApplicationData = await teamApplicationDataSchema.validate(body) + + const updatedTeamApplication = await updateTeamApplication({ where: { id }, data: teamApplicationData }) + + const response = NextResponse.json(updatedTeamApplication, { status: 200 }) + return response + } catch (error) { + return handleError(error) + } +} + +export const DELETE = async (Request: Request, { params }: TeamApplicationRouteData): Promise => { + try { + const { id } = await teamApplicationParamsSchema.validate(params) + + await deleteTeamApplication({ where: { id } }) + + return new NextResponse(null, { status: 204 }) + } catch (error) { + return handleError(error) + } +} diff --git a/src/app/api/teamAplications/route.ts b/src/app/api/teamAplications/route.ts new file mode 100644 index 0000000..75e5e6f --- /dev/null +++ b/src/app/api/teamAplications/route.ts @@ -0,0 +1,43 @@ +import { handleError } from '@/utilities/apiRoutes' +import { teamApplicationDataSchema, teamApplicationSearchParamsSchema } from '@/utilities/teamApplication' +import { createTeamApplication, getTeamApplications, type TeamApplication } from 'entgamers-database/backend/teamApplication' +import { NextResponse, type NextRequest } from 'next/server' + +export const GET = async (request: NextRequest): Promise => { + try { + const searchParams = request.nextUrl.searchParams.entries() + const validatedParams = await teamApplicationSearchParamsSchema.validate(Object.fromEntries(searchParams)) + const getTeamApplicationsParams = { + skip: validatedParams.skip, + take: validatedParams.take, + where: { + name: validatedParams['where[name]'], + email: validatedParams['where[email]'], + discord: validatedParams['where[discord]'], + role: validatedParams['where[role]'] as TeamApplication['role' ] | undefined, + status: validatedParams['where[status]'] + } + } + + const teamApplications = await getTeamApplications(getTeamApplicationsParams) + + const response = NextResponse.json(teamApplications, { status: 200 }) + return response + } catch (error) { + return handleError(error) + } +} + +export const POST = async (request: NextRequest): Promise => { + try { + const body = await request.json() + const createTeamApplicationData = await teamApplicationDataSchema.validate(body) + + const teamApplication = await createTeamApplication({ data: createTeamApplicationData }) + + const response = NextResponse.json(teamApplication, { status: 201 }) + return response + } catch (error) { + return handleError(error) + } +} diff --git a/src/app/equipo/page.tsx b/src/app/equipo/page.tsx index 37da8ac..7815a52 100644 --- a/src/app/equipo/page.tsx +++ b/src/app/equipo/page.tsx @@ -1,12 +1,40 @@ import Typography from '@/components/ui/Typography' -import { css } from '@/styled-system/css' +import { css, cx } from '@/styled-system/css' import { Container } from '@/styled-system/jsx' import { center } from '@/styled-system/patterns' -import { button } from '@/styled-system/recipes' +import { button, card } from '@/styled-system/recipes' +import { getClanMembers, getClanes } from 'entgamers-database/backend/clanes' +import { getUser, type UserWithPreferencesList } from 'entgamers-database/backend/users' +import NextImage from 'next/image' import NextLink from 'next/link' +import { type Models } from 'node-appwrite' import { type FC } from 'react' +interface GetTeamsResponse { + admins: UserWithPreferencesList + moderators: UserWithPreferencesList + collaborators: UserWithPreferencesList +} + +const getTeams = async (): Promise => { + const allClanes = await getClanes() + const adminClanId = allClanes.teams.find(clan => clan.name === 'Admin')?.$id + const moderatorClanId = allClanes.teams.find(clan => clan.name === 'Moderator')?.$id + const collaboratorClanId = allClanes.teams.find(clan => clan.name === 'Collaborator')?.$id + const adminMembers: Models.MembershipList = adminClanId !== undefined ? await getClanMembers(adminClanId) : { total: 0, memberships: [] } + const moderatorMembers: Models.MembershipList = moderatorClanId !== undefined ? await getClanMembers(moderatorClanId) : { total: 0, memberships: [] } + const collaboratorMembers: Models.MembershipList = collaboratorClanId !== undefined ? await getClanMembers(collaboratorClanId) : { total: 0, memberships: [] } + const adminsPromises = adminMembers.memberships.map(async membership => await getUser(membership.userId)) + const moderatorsPromises = moderatorMembers.memberships.map(async membership => await getUser(membership.userId)) + const collaboratorsPromises = collaboratorMembers.memberships.map(async membership => await getUser(membership.userId)) + const [admins, moderators, collaborators] = await Promise.all([ + Promise.all(adminsPromises), Promise.all(moderatorsPromises), Promise.all(collaboratorsPromises) + ]) + return { admins: { total: admins.length, users: admins }, moderators: { total: moderators.length, users: moderators }, collaborators: { total: collaborators.length, users: collaborators } } +} + const EquipoPage: FC = async () => { + const { admins } = await getTeams() return ( Equipo @@ -17,7 +45,67 @@ const EquipoPage: FC = async () => { Los administradores son quienes se encargan de que todo funcione como es debido en la comunidad, desde la moderación de los grupos hasta la organización de eventos. - = 1 + ? ( + + {admins.users.map((user, index) => ( +
+
+ +
+
+ {user.name !== '' ? user.name : `Usuario ${index + 1}`} + {user.prefs.bio !== undefined && user.prefs.bio !== '' && ( + {user.prefs.bio} + )} +
+
+ ))} +
+ ) + : ( + <> + + Ups, parece que ahora mismo no hay administradores, pero en EntGamers siempre estamos estamos buscando gente que quiera organizar cosas para la comunidad, puedes contactarnos para formar parte de nuestro equipo haciendo click en el siguiente enlace. + + + ) + } +
+ + ¡Quiero ser Administrador! + +
+ {/* { flexWrap: 'wrap' })} > - {/* {team.map((member, index) => ( + {team.map((member, index) => (
{
- ))} */} -
-
- - ¡Quiero ser administrador! - -
+ ))} +
*/} Moderadores Los moderadores son los encargados de mantener el orden en los grupos de la comunidad, así como de ayudar a los usuarios a resolver sus dudas. @@ -90,7 +170,7 @@ const EquipoPage: FC = async () => {
¡Quiero ser moderador! @@ -105,7 +185,7 @@ const EquipoPage: FC = async () => {
¡Quiero ser colaborador! diff --git a/src/app/equipo/unirse/ApplyForm.tsx b/src/app/equipo/unirse/ApplyForm.tsx index a8585a5..7c43493 100644 --- a/src/app/equipo/unirse/ApplyForm.tsx +++ b/src/app/equipo/unirse/ApplyForm.tsx @@ -1,53 +1,70 @@ 'use client' -import Alert from '@/components/ui/Alert' 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 TextArea from '@/components/ui/form/TextArea' +import { useAppDispatch } from '@/hooks/useAppDispatch' +import { addAlert } from '@/state/feedbackSlice' import { css } from '@/styled-system/css' -import { type Alert as AlertType } from '@/types/feedback' -import { type TeamApplyData } from '@/types/teamApply' +import { teamApplicationDataSchema, type TeamApplicationData } from '@/utilities/teamApplication' import { faChevronRight } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon, type FontAwesomeIconProps } from '@fortawesome/react-fontawesome' -import { AppwriteException } from 'appwrite' +import { nanoid } from '@reduxjs/toolkit' import { useFormik } from 'formik' import { AnimatePresence, motion } from 'framer-motion' import { useSearchParams } from 'next/navigation' -import { useEffect, useState, type FC } from 'react' +import { useEffect, type FC } from 'react' const ApplyForm: FC = () => { const searchParams = useSearchParams() - const [alert, setAlert] = useState(undefined) + const dispatch = useAppDispatch() - const formik = useFormik({ + const formik = useFormik({ initialValues: { name: '', email: '', - discordName: '', + discord: '', message: '', - role: 'administrator' + role: 'Moderator', + status: 'Pending' }, - onSubmit: async (_values) => { + onSubmit: async (values) => { try { - // await createTeamApply(values) + await fetch('/api/teamAplications', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(values) + }) + dispatch(addAlert({ + id: nanoid(), + title: 'Formulario enviado', + message: 'Gracias por interesarte en unirte al equipo', + severity: 'success' + })) } catch (error) { - if (error instanceof AppwriteException) { - setAlert({ + if (error instanceof Error) { + dispatch(addAlert({ + id: nanoid(), title: 'Error al enviar el formulario', message: error.message, severity: 'error' - }) + })) return } console.error('Error al enviar el formulario', error) - setAlert({ - severity: 'error', + dispatch(addAlert({ + id: nanoid(), title: 'Error al enviar el formulario', - message: 'Hubo un error al enviar el formulario, por favor, intenta nuevamente.' - }) + message: 'Error desconocido', + severity: 'error' + })) } - } + }, + validationSchema: teamApplicationDataSchema, + isInitialValid: false }) useEffect(() => { if (searchParams.has('role')) { @@ -73,36 +90,36 @@ const ApplyForm: FC = () => { @@ -114,126 +131,140 @@ const ApplyForm: FC = () => { gap: 'medium' })} > - {alert !== undefined && ( - - {alert.title !== undefined && ( - {alert.title} - )} - {alert.message} - - )} - {formik.submitCount > 0 && ( -
- - - - {formik.touched.name !== undefined && formik.errors.name !== undefined - ? ( - - {formik.errors.name} - - ) - : ( - - Tu nombre. - - )} - - - - - {formik.touched.email !== undefined && formik.errors.email !== undefined - ? ( - - {formik.errors.email} - - ) - : ( - - Tu email, para poder contactarte. - - ) - } - - - - - {formik.touched.discordName !== undefined && formik.errors.discordName !== undefined - ? ( - - {formik.errors.discordName} - - ) - : ( - - Tu nombre de Discord, para poder contactarte. - - ) - } - - - -