diff --git a/opensaas-sh/app_diff/src/admin/dashboards/users/UsersTable.tsx.diff b/opensaas-sh/app_diff/src/admin/dashboards/users/UsersTable.tsx.diff index 0406d79..9f541a4 100644 --- a/opensaas-sh/app_diff/src/admin/dashboards/users/UsersTable.tsx.diff +++ b/opensaas-sh/app_diff/src/admin/dashboards/users/UsersTable.tsx.diff @@ -1,14 +1,6 @@ --- template/app/src/admin/dashboards/users/UsersTable.tsx +++ opensaas-sh/app/src/admin/dashboards/users/UsersTable.tsx -@@ -17,6 +17,7 @@ - const [skip, setskip] = useState(0); - const [page, setPage] = useState(1); - const [email, setEmail] = useState(undefined); -+ - const [isAdminFilter, setIsAdminFilter] = useState(undefined); - const [statusOptions, setStatusOptions] = useState([]); - const { data, isLoading } = useQuery(getPaginatedUsers, { -@@ -223,7 +224,7 @@ +@@ -202,7 +202,7 @@

{user.subscriptionStatus}

diff --git a/opensaas-sh/app_diff/src/user/operations.ts.diff b/opensaas-sh/app_diff/src/user/operations.ts.diff index ebc6924..bccc675 100644 --- a/opensaas-sh/app_diff/src/user/operations.ts.diff +++ b/opensaas-sh/app_diff/src/user/operations.ts.diff @@ -1,15 +1,6 @@ --- template/app/src/user/operations.ts +++ opensaas-sh/app/src/user/operations.ts -@@ -2,7 +2,7 @@ - import { type UpdateIsUserAdminById, type GetPaginatedUsers } from 'wasp/server/operations'; - import { type User } from 'wasp/entities'; - import { HttpError } from 'wasp/server'; --import { subscriptionStatusSchema, type SubscriptionStatus } from '../payment/plans'; -+import { subscriptionStatusSchema } from '../payment/plans'; - import { ensureArgsSchemaOrThrowHttpError } from '../server/validation'; - - const updateUserAdminByIdInputSchema = z.object({ -@@ -33,10 +33,7 @@ +@@ -34,10 +34,7 @@ }; type GetPaginatedUsersOutput = { @@ -21,7 +12,7 @@ totalPages: number; }; -@@ -80,6 +77,7 @@ +@@ -85,6 +82,7 @@ mode: 'insensitive', }, isAdmin, @@ -29,7 +20,7 @@ }, { OR: [ -@@ -103,7 +101,7 @@ +@@ -106,7 +104,7 @@ username: true, isAdmin: true, subscriptionStatus: true, @@ -37,12 +28,4 @@ + stripeId: true, }, orderBy: { - id: 'desc', -@@ -119,6 +117,7 @@ - mode: 'insensitive', - }, - isAdmin, -+ isMockUser: true, - }, - { - OR: [ + username: 'asc', diff --git a/template/app/schema.prisma b/template/app/schema.prisma index 7d2d71d..1da9b4a 100644 --- a/template/app/schema.prisma +++ b/template/app/schema.prisma @@ -110,4 +110,4 @@ model ContactFormMessage { content String isRead Boolean @default(false) repliedAt DateTime? -} \ No newline at end of file +} diff --git a/template/app/src/admin/dashboards/users/UsersTable.tsx b/template/app/src/admin/dashboards/users/UsersTable.tsx index a88e8b3..4661446 100644 --- a/template/app/src/admin/dashboards/users/UsersTable.tsx +++ b/template/app/src/admin/dashboards/users/UsersTable.tsx @@ -13,26 +13,29 @@ function AdminSwitch({ id, isAdmin }: Pick) { ); } -function UsersTable() { - const [skip, setskip] = useState(0); - const [page, setPage] = useState(1); - const [email, setEmail] = useState(undefined); +const UsersTable = () => { + const [currentPage, setCurrentPage] = useState(1); + const [emailFilter, setEmailFilter] = useState(''); const [isAdminFilter, setIsAdminFilter] = useState(undefined); - const [statusOptions, setStatusOptions] = useState([]); + const [subscriptionStatusFilter, setSubcriptionStatusFilter] = useState([]); + + const skipPages = currentPage - 1; + const { data, isLoading } = useQuery(getPaginatedUsers, { - skip, - emailContains: email, - isAdmin: isAdminFilter, - subscriptionStatus: statusOptions?.length > 0 ? statusOptions : undefined, + skipPages, + filter: { + ...(emailFilter && { emailContains: emailFilter }), + ...(isAdminFilter !== undefined && { isAdmin: isAdminFilter }), + ...(subscriptionStatusFilter.length > 0 && { subscriptionStatusIn: subscriptionStatusFilter }), + }, }); - useEffect(() => { - setPage(1); - }, [email, statusOptions]); - - useEffect(() => { - setskip((page - 1) * 10); - }, [page]); + useEffect( + function backToPageOne() { + setCurrentPage(1); + }, + [emailFilter, subscriptionStatusFilter, isAdminFilter] + ); return (
@@ -49,7 +52,8 @@ function UsersTable() { id='email-filter' placeholder='dude@example.com' onChange={(e) => { - setEmail(e.currentTarget.value); + const value = e.currentTarget.value; + setEmailFilter(value === '' ? undefined : value); }} className='rounded border border-stroke py-2 px-5 bg-white outline-none transition focus:border-primary active:border-primary disabled:cursor-default disabled:bg-whiter dark:border-form-strokedark dark:bg-form-input dark:focus:border-primary' /> @@ -58,8 +62,8 @@ function UsersTable() {
- {!!statusOptions && statusOptions.length > 0 ? ( - statusOptions.map((opt) => ( + {subscriptionStatusFilter.length > 0 ? ( + subscriptionStatusFilter.map((opt) => ( { e.stopPropagation(); - setStatusOptions((prevValue) => { + setSubcriptionStatusFilter((prevValue) => { return prevValue?.filter((val) => val !== opt); }); }} className='z-30 cursor-pointer pl-2 hover:text-danger' > - - - + )) @@ -100,7 +91,7 @@ function UsersTable() { - - - - - +
@@ -170,11 +146,14 @@ function UsersTable() { page { - setPage(parseInt(e.currentTarget.value)); + const value = parseInt(e.currentTarget.value); + if (data?.totalPages && value <= data?.totalPages && value > 0) { + setCurrentPage(value); + } }} className='rounded-md border-1 border-stroke bg-transparent px-4 font-medium outline-none transition focus:border-primary active:border-primary dark:border-form-strokedark dark:bg-form-input dark:focus:border-primary' /> @@ -238,6 +217,34 @@ function UsersTable() {
); +}; + +function ChevronDownIcon() { + return ( + + + + + + ); +} + +function XIcon() { + return ( + + + + ); } export default UsersTable; diff --git a/template/app/src/user/operations.ts b/template/app/src/user/operations.ts index e85cb27..1c0f906 100644 --- a/template/app/src/user/operations.ts +++ b/template/app/src/user/operations.ts @@ -1,8 +1,9 @@ import * as z from 'zod'; import { type UpdateIsUserAdminById, type GetPaginatedUsers } from 'wasp/server/operations'; import { type User } from 'wasp/entities'; -import { HttpError } from 'wasp/server'; +import { HttpError, prisma } from 'wasp/server'; import { subscriptionStatusSchema, type SubscriptionStatus } from '../payment/plans'; +import { type Prisma } from '@prisma/client'; import { ensureArgsSchemaOrThrowHttpError } from '../server/validation'; const updateUserAdminByIdInputSchema = z.object({ @@ -41,11 +42,12 @@ type GetPaginatedUsersOutput = { }; const getPaginatorArgsSchema = z.object({ - skip: z.number(), - cursor: z.number().optional(), - emailContains: z.string().nonempty().optional(), - isAdmin: z.boolean().optional(), - subscriptionStatus: z.array(subscriptionStatusSchema).optional(), + skipPages: z.number(), + filter: z.object({ + emailContains: z.string().nonempty().optional(), + isAdmin: z.boolean().optional(), + subscriptionStatusIn: z.array(subscriptionStatusSchema.nullable()).optional(), + }), }); type GetPaginatedUsersInput = z.infer; @@ -54,29 +56,32 @@ export const getPaginatedUsers: GetPaginatedUsers { - const { skip, emailContains, isAdmin, subscriptionStatus } = ensureArgsSchemaOrThrowHttpError( - getPaginatorArgsSchema, - rawArgs - ); - - if (!context.user?.isAdmin) { - throw new HttpError(401); + if (!context.user) { + throw new HttpError(401, 'Only authenticated users are allowed to perform this operation'); } - const allSubscriptionStatusOptions = subscriptionStatus; - const hasNotSubscribed = allSubscriptionStatusOptions?.find((status) => status === null); - const subscriptionStatusStrings = allSubscriptionStatusOptions?.filter((status) => status !== null) as - | string[] - | undefined; + if (!context.user.isAdmin) { + throw new HttpError(403, 'Only admins are allowed to perform this operation'); + } - const queryResults = await context.entities.User.findMany({ - skip, - take: 10, + const { + skipPages, + filter: { subscriptionStatusIn: subscriptionStatus, emailContains, isAdmin }, + } = ensureArgsSchemaOrThrowHttpError(getPaginatorArgsSchema, rawArgs); + + const includeUnsubscribedUsers = !!subscriptionStatus?.some((status) => status === null); + const desiredSubscriptionStatuses = subscriptionStatus?.filter((status) => status !== null); + + const pageSize = 10; + + const userPageQuery: Prisma.UserFindManyArgs = { + skip: skipPages * pageSize, + take: pageSize, where: { AND: [ { email: { - contains: emailContains || undefined, + contains: emailContains, mode: 'insensitive', }, isAdmin, @@ -85,13 +90,11 @@ export const getPaginatedUsers: GetPaginatedUsers