refactor: team applications

This commit is contained in:
2024-07-30 18:08:53 -06:00
parent 7a87eac395
commit c979e6540f
12 changed files with 268 additions and 275 deletions
@@ -1,44 +0,0 @@
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<Response> => {
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 PUT = async (Request: Request, { params }: TeamApplicationRouteData): Promise<Response> => {
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<Response> => {
try {
const { id } = await teamApplicationParamsSchema.validate(params)
await deleteTeamApplication({ where: { id } })
return new NextResponse(null, { status: 204 })
} catch (error) {
return handleError(error)
}
}
-43
View File
@@ -1,43 +0,0 @@
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<Response> => {
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<Response> => {
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)
}
}
@@ -1,4 +1,4 @@
import ApplicationsList from '@/app/dashboard/_components/ApplicationsList' import ApplicationsList from '@/app/dashboard/_components/teamApplications/ApplicationsList'
import Typography from '@/components/ui/Typography' import Typography from '@/components/ui/Typography'
import { type FC } from 'react' import { type FC } from 'react'
@@ -0,0 +1,48 @@
import DebouncedInput from '@/components/ui/form/DebouncedInput'
import { type Column } from '@tanstack/react-table'
import { type TeamApplication, type TeamApplicationRole, type TeamApplicationStatus } from 'entgamers-database/types/teamApplications'
import { type FC } from 'react'
import RoleSelector from './RoleSelector'
import StatusSelector from './StatusSelector'
export interface ApplicationsFilterProps {
column: Column<TeamApplication, unknown>
}
const ApplicationsFilter: FC<ApplicationsFilterProps> = ({ column }) => {
const columnFilterValue = column.getFilterValue()
switch (column.id) {
case 'status':
return (
<StatusSelector
id={`${column.id}-status-filter`}
value={columnFilterValue as TeamApplicationStatus}
onChange={value => { column.setFilterValue(value) }}
allowEmpty
/>
)
case 'role':
return (
<RoleSelector
id={`${column.id}-role-filter`}
value={columnFilterValue as TeamApplicationRole}
onChange={value => { column.setFilterValue(value) }}
allowEmpty
/>
)
default:
return (
<DebouncedInput
fullWidth
type="text"
value={(columnFilterValue ?? '') as string}
onChange={value => { column.setFilterValue(value) }}
placeholder="Buscar..."
className="w-36 border shadow rounded"
list={column.id + 'list'}
/>
)
}
}
export default ApplicationsFilter
@@ -1,120 +1,40 @@
import IconButton from '@/components/ui/IconButton' import IconButton from '@/components/ui/IconButton'
import { Table, TableBody, TableCell, TableContainer, TableHead, TableHeadCell, TableRow } from '@/components/ui/Table' import { Table, TableBody, TableCell, TableContainer, TableHead, TableHeadCell, TableRow } from '@/components/ui/Table'
import DebouncedInput from '@/components/ui/form/DebouncedInput'
import useManageError from '@/hooks/useManageError' import useManageError from '@/hooks/useManageError'
import { css } from '@/styled-system/css' import { css } from '@/styled-system/css'
import { formatDate } from '@/utilities/date' import { formatDate } from '@/utilities/date'
import { type TeamApplication, type TeamApplicationList } from '@/utilities/teamApplication' import { type TeamApplication, type TeamApplicationList } from '@/utilities/teamApplication'
import { faChevronLeft, faChevronRight, faSort, faSortAsc, faSortDesc } from '@fortawesome/free-solid-svg-icons' import { faChevronLeft, faChevronRight, faSort, faSortAsc, faSortDesc } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { rankItem } from '@tanstack/match-sorter-utils' import { createColumnHelper, flexRender, getCoreRowModel, useReactTable, type ColumnFiltersState, type PaginationState, type RowData, type SortingState } from '@tanstack/react-table'
import { createColumnHelper, flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, type Column, type FilterFn, type Table as TableType } from '@tanstack/react-table' import { getAllTeamApplications, updateTeamApplication } from 'entgamers-database/frontend/database/teamApplications'
import { useCallback, useEffect, useMemo, useState, type FC, type ReactNode } from 'react' import { Query } from 'entgamers-database/lib/appwrite'
import { useEffect, useState, type FC } from 'react'
import ApplicationsFilter from './ApplicationsFilter'
import StatusUpdater from './StatusUpdater'
const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => { declare module '@tanstack/table-core' {
// Rank the item interface TableMeta<TData extends RowData> {
const itemRank = rankItem(row.getValue(columnId), value as string) updateRow: (id: string, value: Partial<TData>) => Promise<void>
}
// Store the itemRank info
addMeta({
itemRank
})
// Return if the item should be filtered in/out
return itemRank.passed
}
function Filter ({
column,
table
}: {
column: Column<any, unknown>
table: TableType<any>
}): ReactNode {
const firstValue = table
.getPreFilteredRowModel()
.flatRows[0]?.getValue(column.id)
const columnFilterValue = column.getFilterValue()
const sortedUniqueValues = useMemo(() => typeof firstValue === 'number'
? []
// eslint-disable-next-line @typescript-eslint/require-array-sort-compare
: Array.from(column.getFacetedUniqueValues().keys()).sort()
, [column.getFacetedUniqueValues()])
return typeof firstValue === 'number'
? (
<div>
<div className="flex space-x-2">
<DebouncedInput
fullWidth
type="number"
min={Number(column.getFacetedMinMaxValues()?.[0] ?? '')}
max={Number(column.getFacetedMinMaxValues()?.[1] ?? '')}
value={(columnFilterValue as [number, number])?.[0] ?? ''}
onChange={value => { column.setFilterValue((old: [number, number]) => [value, old?.[1]]) }
}
placeholder={`Min ${
((column.getFacetedMinMaxValues()?.[0]) != null)
? `(${column.getFacetedMinMaxValues()?.[0]})`
: ''
}`}
className="w-24 border shadow rounded"
/>
<DebouncedInput
fullWidth
type="number"
min={Number(column.getFacetedMinMaxValues()?.[0] ?? '')}
max={Number(column.getFacetedMinMaxValues()?.[1] ?? '')}
value={(columnFilterValue as [number, number])?.[1] ?? ''}
onChange={value => { column.setFilterValue((old: [number, number]) => [old?.[0], value]) }
}
placeholder={`Max ${
((column.getFacetedMinMaxValues()?.[1]) != null)
? `(${column.getFacetedMinMaxValues()?.[1]})`
: ''
}`}
className="w-24 border shadow rounded"
/>
</div>
<div className="h-1" />
</div>
)
: (
<>
<datalist id={column.id + 'list'}>
{sortedUniqueValues.slice(0, 5000).map((value: any) => (
<option value={value} key={value} />
))}
</datalist>
<DebouncedInput
fullWidth
type="text"
value={(columnFilterValue ?? '') as string}
onChange={value => { column.setFilterValue(value) }}
placeholder={`Search... (${column.getFacetedUniqueValues().size})`}
className="w-36 border shadow rounded"
list={column.id + 'list'}
/>
<div className="h-1" />
</>
)
} }
const columnHelper = createColumnHelper<TeamApplication>() const columnHelper = createColumnHelper<TeamApplication>()
const columns = [ const columns = [
columnHelper.accessor('id', { columnHelper.accessor('$id', {
header: 'ID' header: 'ID',
enableColumnFilter: false
}), }),
columnHelper.accessor('status', { columnHelper.accessor('status', {
header: 'Estado', header: 'Estado',
enableSorting: false cell: StatusUpdater,
getUniqueValues () {
return ['Pending', 'Accepted', 'Rejected']
}
}), }),
columnHelper.accessor('role', { columnHelper.accessor('role', {
header: 'Rol', header: 'Rol'
enableSorting: false
}), }),
columnHelper.accessor('name', { columnHelper.accessor('name', {
header: 'Nombre' header: 'Nombre'
@@ -129,14 +49,16 @@ const columns = [
columnHelper.accessor('discord', { columnHelper.accessor('discord', {
header: 'Discord' header: 'Discord'
}), }),
columnHelper.accessor('createdAt', { columnHelper.accessor('$createdAt', {
header: 'Creado', header: 'Creado',
enableColumnFilter: false,
cell: (info) => { cell: (info) => {
return formatDate(new Date(info.getValue())) return formatDate(new Date(info.getValue()))
} }
}), }),
columnHelper.accessor('updatedAt', { columnHelper.accessor('$updatedAt', {
header: 'Actualizado', header: 'Actualizado',
enableColumnFilter: false,
cell: (info) => { cell: (info) => {
return formatDate(new Date(info.getValue())) return formatDate(new Date(info.getValue()))
} }
@@ -145,46 +67,65 @@ const columns = [
const ApplicationsList: FC = () => { const ApplicationsList: FC = () => {
const { manageError } = useManageError() const { manageError } = useManageError()
const [applications, setApplications] = useState<TeamApplication[]>([]) const [pagination, setPagination] = useState<PaginationState>({ pageIndex: 0, pageSize: 10 })
const [sorting, setSorting] = useState<SortingState>([{ id: '$createdAt', desc: true }])
const [applications, setApplications] = useState<TeamApplicationList>({ total: 0, documents: [] })
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([{ id: 'status', value: 'Pending' }])
const table = useReactTable({ const table = useReactTable({
data: applications, data: applications.documents,
columns, columns,
filterFns: {
fuzzy: fuzzyFilter
},
initialState: { initialState: {
columnVisibility: { columnVisibility: {
id: false $id: false,
}, email: false
sorting: [{ id: 'createdAt', desc: true }] }
}, },
getCoreRowModel: getCoreRowModel(), state: {
getPaginationRowModel: getPaginationRowModel(), pagination,
getSortedRowModel: getSortedRowModel(), sorting,
getFilteredRowModel: getFilteredRowModel() columnFilters
},
meta: {
updateRow: async (id: string, value: Partial<TeamApplication>) => {
const updatedTeamApplication = await updateTeamApplication(id, value)
const newApplications = applications.documents.map((application) => application.$id === updatedTeamApplication.$id ? updatedTeamApplication : application)
setApplications({ total: applications.total, documents: newApplications })
}
},
manualPagination: true,
rowCount: applications.total,
onPaginationChange: setPagination,
enableSorting: true,
manualSorting: true,
onSortingChange: setSorting,
manualFiltering: true,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel()
}) })
const getTeamApplications = useCallback(async (controller?: AbortController) => {
const callController = controller ?? new AbortController()
const response = await fetch('/api/team-applications', { signal: callController.signal })
if (response.ok) {
const teamApplicationList: TeamApplicationList = await response.json()
setApplications(teamApplicationList.teamApplications)
}
}, [])
useEffect(() => { useEffect(() => {
const controller = new AbortController() const query: string[] = [
getTeamApplications(controller) Query.limit(pagination.pageSize),
Query.offset(pagination.pageIndex * pagination.pageSize)
]
if (sorting.length > 0) {
const sort: string = sorting[0].desc ? Query.orderDesc(sorting[0].id) : Query.orderAsc(sorting[0].id)
query.push(sort)
}
if (columnFilters.length > 0) {
const filter: string[] = columnFilters.map((columnFilter) => {
return Query.contains(columnFilter.id, columnFilter.value as string)
})
query.push(...filter)
}
getAllTeamApplications(query)
.then((applicationList) => { setApplications(applicationList) })
.catch((error) => { .catch((error) => {
if (error instanceof Error && error.name === 'AbortError') return if (error instanceof Error && error.name === 'AbortError') return
manageError(error, 'Error al obtener las aplicaciones', 'Error desconocido al obtener las aplicaciones', 'error') manageError(error, 'Error al obtener las aplicaciones', 'Error desconocido al obtener las aplicaciones', 'error')
}) })
return () => { }, [pagination, sorting, columnFilters])
controller.abort()
}
}, [])
// TODO: Better UI Controls for: column visibility. Quantity selector. // TODO: Better UI Controls for: column visibility. Quantity selector.
return ( return (
@@ -218,6 +159,7 @@ const ApplicationsList: FC = () => {
<TableHeadCell <TableHeadCell
key={header.id} key={header.id}
className={css({ className={css({
verticalAlign: 'top',
position: 'relative', position: 'relative',
'&:hover > [data-is-resizing]': { '&:hover > [data-is-resizing]': {
backgroundColor: 'border' backgroundColor: 'border'
@@ -228,26 +170,30 @@ const ApplicationsList: FC = () => {
<div <div
className={css({ className={css({
display: 'flex', display: 'flex',
alignItems: 'center', flexDirection: 'column'
gap: 'small'
})} })}
> >
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) } <div
{header.column.getCanSort() && ( className={css({
<IconButton display: 'flex',
size="small" alignItems: 'center',
onClick={header.column.getToggleSortingHandler()} justifyContent: 'space-between',
> gap: 'small'
<FontAwesomeIcon icon={header.column.getIsSorted() === 'asc' ? faSortAsc : header.column.getIsSorted() === 'desc' ? faSortDesc : faSort} size="sm" fixedWidth /> })}
</IconButton> >
)} {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) }
</div> {header.column.getCanSort() && (
<div> <IconButton
size="small"
onClick={header.column.getToggleSortingHandler()}
>
<FontAwesomeIcon icon={header.column.getIsSorted() === 'asc' ? faSortAsc : header.column.getIsSorted() === 'desc' ? faSortDesc : faSort} size="sm" fixedWidth />
</IconButton>
)}
</div>
{header.column.getCanFilter() {header.column.getCanFilter()
? ( ? (
<div> <ApplicationsFilter column={header.column}/>
<Filter column={header.column} table={table} />
</div>
) )
: null : null
} }
@@ -0,0 +1,46 @@
import { css } from '@/styled-system/css'
import { type TeamApplicationRole } from 'entgamers-database/types/teamApplications'
import { type FC } from 'react'
export interface RoleSelectorProps {
id: string
value: TeamApplicationRole
onChange: (role: TeamApplicationRole) => void
allowEmpty?: boolean
}
const RoleSelector: FC<RoleSelectorProps> = ({ id, value, onChange, allowEmpty }) => {
/* TODO: Change for Select UI Component when it's ready */
return (
<select
id={`${id}-status`}
className={css({
width: '100%',
border: 'none',
background: 'transparent',
color: 'inherit',
outline: 'none',
cursor: 'pointer',
fontSize: 'inherit',
fontWeight: 'inherit',
lineHeight: 'inherit',
padding: '0',
borderRadius: '0',
'&:focus': {
outline: 'none'
}
})}
value={value}
onChange={(event) => {
onChange(event.target.value as TeamApplicationRole)
}}
>
{allowEmpty === true && <option value="">Todos</option>}
<option value="Admin">Administrador</option>
<option value="Collaborator">Colaborador</option>
<option value="Moderator">Moderador</option>
</select>
)
}
export default RoleSelector
@@ -0,0 +1,46 @@
import { css } from '@/styled-system/css'
import { type TeamApplicationStatus } from 'entgamers-database/types/teamApplications'
import { type FC } from 'react'
export interface StatusSelectorProps {
id: string
value: TeamApplicationStatus
onChange: (status: TeamApplicationStatus) => void
allowEmpty?: boolean
}
const StatusSelector: FC<StatusSelectorProps> = ({ id, value, onChange, allowEmpty }) => {
/* TODO: Change for Select UI Component when it's ready */
return (
<select
id={`${id}-status`}
className={css({
width: '100%',
border: 'none',
background: 'transparent',
color: 'inherit',
outline: 'none',
cursor: 'pointer',
fontSize: 'inherit',
fontWeight: 'inherit',
lineHeight: 'inherit',
padding: '0',
borderRadius: '0',
'&:focus': {
outline: 'none'
}
})}
value={value}
onChange={(event) => {
onChange(event.target.value as TeamApplicationStatus)
}}
>
{allowEmpty === true && <option value="">Todos</option>}
<option value="Pending">Pendiente</option>
<option value="Accepted">Aceptado</option>
<option value="Rejected">Rechazado</option>
</select>
)
}
export default StatusSelector
@@ -0,0 +1,21 @@
import { type CellContext } from '@tanstack/react-table'
import { type TeamApplication, type TeamApplicationStatus } from 'entgamers-database/types/teamApplications'
import { type FC } from 'react'
import StatusSelector from './StatusSelector'
const StatusUpdater: FC<CellContext<TeamApplication, TeamApplicationStatus>> = ({ cell: { id, row }, table }) => {
return (
<>
<StatusSelector
id={`${id}-status`}
value={row.original.status}
onChange={(status) => {
table.options.meta?.updateRow(row.original.$id, { status })
.catch(console.error)
}}
/>
</>
)
}
export default StatusUpdater
+4 -3
View File
@@ -4,8 +4,9 @@ import { Container } from '@/styled-system/jsx'
import { center } from '@/styled-system/patterns' import { center } from '@/styled-system/patterns'
import { button, card } from '@/styled-system/recipes' import { button, card } from '@/styled-system/recipes'
import { getClanMembers } from 'entgamers-database/backend/clanes' import { getClanMembers } from 'entgamers-database/backend/clanes'
import { ADMIN_CLAN_ID, COLLABORATOR_CLAN_ID, MODERATOR_CLAN_ID, ensureAdministrativeClans } from 'entgamers-database/backend/clanes/administrative' import { getUser } from 'entgamers-database/backend/users'
import { getUser, type UserList } from 'entgamers-database/backend/users' import { ADMIN_CLAN_ID, COLLABORATOR_CLAN_ID, MODERATOR_CLAN_ID } from 'entgamers-database/lib/env'
import { type UserList } from 'entgamers-database/types/user'
import NextImage from 'next/image' import NextImage from 'next/image'
import NextLink from 'next/link' import NextLink from 'next/link'
import { type Models } from 'node-appwrite' import { type Models } from 'node-appwrite'
@@ -18,7 +19,7 @@ interface GetTeamsResponse {
} }
const getTeams = async (): Promise<GetTeamsResponse> => { const getTeams = async (): Promise<GetTeamsResponse> => {
await ensureAdministrativeClans() // await ensureAdministrativeClans()
const adminMembers: Models.MembershipList = await getClanMembers(ADMIN_CLAN_ID) const adminMembers: Models.MembershipList = await getClanMembers(ADMIN_CLAN_ID)
const moderatorMembers: Models.MembershipList = await getClanMembers(MODERATOR_CLAN_ID) const moderatorMembers: Models.MembershipList = await getClanMembers(MODERATOR_CLAN_ID)
+5 -11
View File
@@ -8,11 +8,12 @@ import { useAppDispatch } from '@/hooks/useAppDispatch'
import useManageError from '@/hooks/useManageError' import useManageError from '@/hooks/useManageError'
import { addAlert } from '@/state/feedbackSlice' import { addAlert } from '@/state/feedbackSlice'
import { css } from '@/styled-system/css' import { css } from '@/styled-system/css'
import { teamApplicationDataSchema, type TeamApplicationData } from '@/utilities/teamApplication' import { teamApplicationDataSchema } from '@/utilities/teamApplication'
import { faChevronRight } from '@fortawesome/free-solid-svg-icons' import { faChevronRight } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon, type FontAwesomeIconProps } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon, type FontAwesomeIconProps } from '@fortawesome/react-fontawesome'
import { nanoid } from '@reduxjs/toolkit' import { nanoid } from '@reduxjs/toolkit'
import { ADMIN_CLAN_ID, COLLABORATOR_CLAN_ID, MODERATOR_CLAN_ID } from 'entgamers-database/frontend/clanes/administrative' import { ADMIN_CLAN_ID, COLLABORATOR_CLAN_ID, MODERATOR_CLAN_ID } from 'entgamers-database/frontend/clanes/administrative'
import { createTeamApplication, type TeamApplicationData } from 'entgamers-database/frontend/database/teamApplications'
import { useFormik } from 'formik' import { useFormik } from 'formik'
import { AnimatePresence, motion } from 'framer-motion' import { AnimatePresence, motion } from 'framer-motion'
import { useSearchParams } from 'next/navigation' import { useSearchParams } from 'next/navigation'
@@ -23,24 +24,17 @@ const ApplyForm: FC = () => {
const { manageError } = useManageError() const { manageError } = useManageError()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const formik = useFormik<TeamApplicationData>({ const formik = useFormik <Omit<TeamApplicationData, 'status'>>({
initialValues: { initialValues: {
name: '', name: '',
email: '', email: '',
discord: '', discord: '',
message: '', message: '',
role: 'Moderator', role: 'Moderator'
status: 'Pending'
}, },
onSubmit: async (values) => { onSubmit: async (values) => {
try { try {
await fetch('/api/team-applications', { await createTeamApplication(values)
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(values)
})
dispatch(addAlert({ dispatch(addAlert({
id: nanoid(), id: nanoid(),
title: 'Formulario enviado', title: 'Formulario enviado',
+3 -1
View File
@@ -1,9 +1,11 @@
import Typography from '@/components/ui/Typography' import Typography from '@/components/ui/Typography'
import { Container } from '@/styled-system/jsx' import { Container } from '@/styled-system/jsx'
import { ensureTeamApplicationsCollection } from 'entgamers-database/backend/database/teamApplications'
import { type FC } from 'react' import { type FC } from 'react'
import ApplyForm from './ApplyForm' import ApplyForm from './ApplyForm'
const EquipoUnirsePage: FC = () => { const EquipoUnirsePage: FC = async () => {
await ensureTeamApplicationsCollection()
return ( return (
<Container> <Container>
<Typography variant="h1" align="center">Únete al Bosque</Typography> <Typography variant="h1" align="center">Únete al Bosque</Typography>
+5 -29
View File
@@ -1,45 +1,21 @@
import { type PaginationOptions } from '@/types/api' import type { TeamApplication, TeamApplicationList } from 'entgamers-database/types/teamApplications'
import { type TeamApplication, type TeamApplicationList } from 'entgamers-database/backend/teamApplication' import { object, string, type ObjectSchema } from 'yup'
import { number, object, string, type ObjectSchema } from 'yup'
export interface TeamApplicationDynamicParams { export interface TeamApplicationDynamicParams {
id: string id: string
} }
export interface TeamApplicationRouteData {
params: TeamApplicationDynamicParams
}
export interface TeamApplicationSearchParams extends PaginationOptions {
'where[name]'?: string
'where[email]'?: string
'where[discord]'?: string
'where[role]'?: string
'where[status]'?: string
}
export type TeamApplicationData = Omit<TeamApplication, 'id' | 'createdAt' | 'updatedAt' >
export { type TeamApplication, type TeamApplicationList } export { type TeamApplication, type TeamApplicationList }
export const teamApplicationDataSchema: ObjectSchema<TeamApplicationData> = object({ export const teamApplicationDataSchema = object({
name: string().required('El nombre es obligatorio'), name: string().required('El nombre es obligatorio'),
email: string().email('Invalid email').required('El email es obligatorio'), email: string().email('Invalid email').required('El email es obligatorio'),
discord: string().required('El discord es obligatorio'), discord: string().required('El discord es obligatorio'),
message: string().required('El mensaje es obligatorio'), message: string().required('El mensaje es obligatorio').max(4096, 'El mensaje debe ser menor a 4096 caracteres'),
role: string().oneOf(['Admin', 'Moderator', 'Collaborator', 'User'], 'Rol inválido').required('El rol es obligatorio'), role: string().oneOf(['Admin', 'Moderator', 'Collaborator'], 'Role inválido').required('El rol es obligatorio'),
status: string().default('Pending').oneOf(['Pending', 'Accepted', 'Rejected'], 'Status inválido') status: string().default('Pending').oneOf(['Pending', 'Accepted', 'Rejected'], 'Status inválido')
}) })
export const teamApplicationParamsSchema: ObjectSchema<TeamApplicationDynamicParams> = object({ export const teamApplicationParamsSchema: ObjectSchema<TeamApplicationDynamicParams> = object({
id: string().required('El id es obligatorio') id: string().required('El id es obligatorio')
}) })
export const teamApplicationSearchParamsSchema: ObjectSchema<TeamApplicationSearchParams> = object({
skip: number().optional().transform((value) => Number(value)),
take: number().optional().transform((value) => Number(value)),
'where[name]': string().optional(),
'where[email]': string().optional(),
'where[discord]': string().optional(),
'where[role]': string().optional().oneOf(['Admin', 'Moderator', 'Collaborator', 'User'], 'Rol inválido'),
'where[status]': string().optional().oneOf(['Pending', 'Accepted', 'Rejected'], 'Status inválido')
}).partial()