Merge pull request #378 from wasp-lang/filip-refactor-user-module

This commit is contained in:
Filip Sodić 2025-02-24 15:41:16 +01:00 committed by GitHub
commit 64dcb48234
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 208 additions and 173 deletions

View File

@ -1,14 +1,14 @@
--- template/app/src/admin/dashboards/users/UsersTable.tsx
+++ opensaas-sh/app/src/admin/dashboards/users/UsersTable.tsx
@@ -9,6 +9,7 @@
@@ -17,6 +17,7 @@
const [skip, setskip] = useState(0);
const [page, setPage] = useState(1);
const [email, setEmail] = useState<string | undefined>(undefined);
+
const [isAdminFilter, setIsAdminFilter] = useState<boolean | undefined>(undefined);
const [statusOptions, setStatusOptions] = useState<SubscriptionStatus[]>([]);
const { data, isLoading, error } = useQuery(getPaginatedUsers, {
@@ -211,7 +212,7 @@
const { data, isLoading } = useQuery(getPaginatedUsers, {
@@ -223,7 +224,7 @@
<p className='text-sm text-black dark:text-white'>{user.subscriptionStatus}</p>
</div>
<div className='col-span-2 flex items-center'>

View File

@ -1,18 +1,27 @@
--- template/app/src/user/operations.ts
+++ opensaas-sh/app/src/user/operations.ts
@@ -41,7 +41,10 @@
@@ -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 @@
};
type GetPaginatedUsersOutput = {
- users: Pick<User, 'id' | 'email' | 'username' | 'subscriptionStatus' | 'paymentProcessorUserId'>[];
+ users: Pick<
+ User,
+ 'id' | 'email' | 'username' | 'subscriptionStatus' | 'stripeId'
+ >[];
- users: Pick<
- User,
- 'id' | 'email' | 'username' | 'subscriptionStatus' | 'paymentProcessorUserId' | 'isAdmin'
- >[];
+ users: Pick<User, 'id' | 'email' | 'username' | 'subscriptionStatus' | 'stripeId' | 'isAdmin'>[];
totalPages: number;
};
@@ -85,6 +88,7 @@
@@ -80,6 +77,7 @@
mode: 'insensitive',
},
isAdmin,
@ -20,7 +29,7 @@
},
{
OR: [
@@ -108,7 +112,7 @@
@@ -103,7 +101,7 @@
username: true,
isAdmin: true,
subscriptionStatus: true,
@ -29,7 +38,7 @@
},
orderBy: {
id: 'desc',
@@ -124,6 +128,7 @@
@@ -119,6 +117,7 @@
mode: 'insensitive',
},
isAdmin,

View File

@ -1,33 +0,0 @@
import { type User } from 'wasp/entities';
import { useState } from 'react';
import { cn } from '../../../client/cn';
const SwitcherOne = ({ user, updateIsUserAdminById }: { user?: Partial<User>; updateIsUserAdminById?: any }) => {
const [enabled, setEnabled] = useState<boolean>(user?.isAdmin || false);
return (
<div className='relative'>
<label htmlFor={`toggle1-${user?.id}`} className='flex cursor-pointer select-none items-center'>
<div className='relative'>
<input
type='checkbox'
id={`toggle1-${user?.id}`}
className='sr-only'
onChange={() => {
setEnabled(!enabled);
updateIsUserAdminById && updateIsUserAdminById({ id: user?.id, data: { isAdmin: !enabled } });
}}
/>
<div className='reblock h-8 w-14 rounded-full bg-meta-9 dark:bg-[#5A616B]'></div>
<div
className={cn('absolute left-1 top-1 h-6 w-6 rounded-full bg-white dark:bg-gray-400 transition', {
'!right-1 !translate-x-full !bg-primary dark:!bg-white': enabled,
})}
></div>
</div>
</label>
</div>
);
};
export default SwitcherOne;

View File

@ -1,17 +1,25 @@
import { type SubscriptionStatus } from '../../../payment/plans';
import { updateIsUserAdminById, useQuery, getPaginatedUsers } from 'wasp/client/operations';
import { useQuery, getPaginatedUsers } from 'wasp/client/operations';
import { useState, useEffect } from 'react';
import SwitcherOne from './SwitcherOne';
import SwitcherOne from '../../elements/forms/SwitcherOne';
import LoadingSpinner from '../../layout/LoadingSpinner';
import DropdownEditDelete from './DropdownEditDelete';
import { updateIsUserAdminById } from 'wasp/client/operations';
import { type User } from 'wasp/entities';
const UsersTable = () => {
function AdminSwitch({ id, isAdmin }: Pick<User, 'id' | 'isAdmin'>) {
return (
<SwitcherOne isOn={isAdmin} onChange={(value) => updateIsUserAdminById({ id: id, isAdmin: value })} />
);
}
function UsersTable() {
const [skip, setskip] = useState(0);
const [page, setPage] = useState(1);
const [email, setEmail] = useState<string | undefined>(undefined);
const [isAdminFilter, setIsAdminFilter] = useState<boolean | undefined>(undefined);
const [statusOptions, setStatusOptions] = useState<SubscriptionStatus[]>([]);
const { data, isLoading, error } = useQuery(getPaginatedUsers, {
const { data, isLoading } = useQuery(getPaginatedUsers, {
skip,
emailContains: email,
isAdmin: isAdminFilter,
@ -51,7 +59,7 @@ const UsersTable = () => {
<div className='flex-grow relative z-20 rounded border border-stroke pr-8 outline-none bg-white transition focus:border-primary active:border-primary dark:border-form-strokedark dark:bg-form-input'>
<div className='flex items-center'>
{!!statusOptions && statusOptions.length > 0 ? (
statusOptions.map((opt, idx) => (
statusOptions.map((opt) => (
<span
key={opt}
className='z-30 flex items-center my-1 mx-2 py-1 px-2 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'
@ -109,7 +117,11 @@ const UsersTable = () => {
<option value=''>Select filters</option>
{['past_due', 'canceled', 'active', 'deleted', null].map((status) => {
if (!statusOptions.includes(status as SubscriptionStatus)) {
return <option value={status || ''}>{status ? status : 'has not subscribed'}</option>;
return (
<option key={status} value={status || ''}>
{status ? status : 'has not subscribed'}
</option>
);
}
})}
</select>
@ -215,7 +227,7 @@ const UsersTable = () => {
</div>
<div className='col-span-1 flex items-center'>
<div className='text-sm text-black dark:text-white'>
<SwitcherOne user={user} updateIsUserAdminById={updateIsUserAdminById} />
<AdminSwitch {...user} />
</div>
</div>
<div className='col-span-1 flex items-center'>
@ -226,6 +238,6 @@ const UsersTable = () => {
</div>
</div>
);
};
}
export default UsersTable;

View File

@ -2,9 +2,10 @@ import { type AuthUser } from 'wasp/auth';
import Breadcrumb from '../../layout/Breadcrumb';
import DefaultLayout from '../../layout/DefaultLayout';
import CheckboxOne from './CheckboxOne';
import SwitcherOne from '../../dashboards/users/SwitcherOne';
import SwitcherTwo from './SwitcherTwo';
import SwitcherOne from './SwitcherOne';
import { useRedirectHomeUnlessUserIsAdmin } from '../../useRedirectHomeUnlessUserIsAdmin';
import { useState } from 'react';
const FormElements = ({ user }: { user: AuthUser }) => {
useRedirectHomeUnlessUserIsAdmin({ user });
@ -56,10 +57,7 @@ const FormElements = ({ user }: { user: AuthUser }) => {
<div className='border-b border-stroke py-4 px-6.5 dark:border-strokedark'>
<h3 className='font-medium text-black dark:text-white'>Toggle switch input</h3>
</div>
<div className='flex flex-col gap-5.5 p-6.5'>
<SwitcherOne />
<SwitcherTwo />
</div>
<SwitchExamples />
</div>
{/* <!-- Time and date --> */}
@ -172,28 +170,7 @@ const FormElements = ({ user }: { user: AuthUser }) => {
<label className='mb-3 block text-black dark:text-white'>Select Country</label>
<div className='relative z-20 bg-white dark:bg-form-input'>
<span className='absolute top-1/2 left-4 z-30 -translate-y-1/2'>
<svg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'>
<g opacity='0.8'>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M10.0007 2.50065C5.85852 2.50065 2.50065 5.85852 2.50065 10.0007C2.50065 14.1428 5.85852 17.5007 10.0007 17.5007C14.1428 17.5007 17.5007 14.1428 17.5007 10.0007C17.5007 5.85852 14.1428 2.50065 10.0007 2.50065ZM0.833984 10.0007C0.833984 4.93804 4.93804 0.833984 10.0007 0.833984C15.0633 0.833984 19.1673 4.93804 19.1673 10.0007C19.1673 15.0633 15.0633 19.1673 10.0007 19.1673C4.93804 19.1673 0.833984 15.0633 0.833984 10.0007Z'
fill='#637381'
></path>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M0.833984 9.99935C0.833984 9.53911 1.20708 9.16602 1.66732 9.16602H18.334C18.7942 9.16602 19.1673 9.53911 19.1673 9.99935C19.1673 10.4596 18.7942 10.8327 18.334 10.8327H1.66732C1.20708 10.8327 0.833984 10.4596 0.833984 9.99935Z'
fill='#637381'
></path>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M7.50084 10.0008C7.55796 12.5632 8.4392 15.0301 10.0006 17.0418C11.5621 15.0301 12.4433 12.5632 12.5005 10.0008C12.4433 7.43845 11.5621 4.97153 10.0007 2.95982C8.4392 4.97153 7.55796 7.43845 7.50084 10.0008ZM10.0007 1.66749L9.38536 1.10547C7.16473 3.53658 5.90275 6.69153 5.83417 9.98346C5.83392 9.99503 5.83392 10.0066 5.83417 10.0182C5.90275 13.3101 7.16473 16.4651 9.38536 18.8962C9.54325 19.069 9.76655 19.1675 10.0007 19.1675C10.2348 19.1675 10.4581 19.069 10.6159 18.8962C12.8366 16.4651 14.0986 13.3101 14.1671 10.0182C14.1674 10.0066 14.1674 9.99503 14.1671 9.98346C14.0986 6.69153 12.8366 3.53658 10.6159 1.10547L10.0007 1.66749Z'
fill='#637381'
></path>
</g>
</svg>
<GlobeIcon />
</span>
<select className='relative z-20 w-full appearance-none rounded border border-stroke bg-transparent py-3 px-12 outline-none transition focus:border-primary active:border-primary dark:border-form-strokedark dark:bg-form-input'>
<option value=''>USA</option>
@ -201,16 +178,7 @@ const FormElements = ({ user }: { user: AuthUser }) => {
<option value=''>Canada</option>
</select>
<span className='absolute top-1/2 right-4 z-10 -translate-y-1/2'>
<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
<g opacity='0.8'>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M5.29289 8.29289C5.68342 7.90237 6.31658 7.90237 6.70711 8.29289L12 13.5858L17.2929 8.29289C17.6834 7.90237 18.3166 7.90237 18.7071 8.29289C19.0976 8.68342 19.0976 9.31658 18.7071 9.70711L12.7071 15.7071C12.3166 16.0976 11.6834 16.0976 11.2929 15.7071L5.29289 9.70711C4.90237 9.31658 4.90237 8.68342 5.29289 8.29289Z'
fill='#637381'
></path>
</g>
</svg>
<ChevronDownIcon />
</span>
</div>
</div>
@ -222,45 +190,26 @@ const FormElements = ({ user }: { user: AuthUser }) => {
<span className='m-1.5 flex items-center justify-center rounded border-[.5px] border-stroke bg-gray py-1.5 px-2.5 text-sm font-medium dark:border-strokedark dark:bg-white/30'>
Design
<span className='cursor-pointer pl-2 hover:text-danger'>
<svg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M9.35355 3.35355C9.54882 3.15829 9.54882 2.84171 9.35355 2.64645C9.15829 2.45118 8.84171 2.45118 8.64645 2.64645L6 5.29289L3.35355 2.64645C3.15829 2.45118 2.84171 2.45118 2.64645 2.64645C2.45118 2.84171 2.45118 3.15829 2.64645 3.35355L5.29289 6L2.64645 8.64645C2.45118 8.84171 2.45118 9.15829 2.64645 9.35355C2.84171 9.54882 3.15829 9.54882 3.35355 9.35355L6 6.70711L8.64645 9.35355C8.84171 9.54882 9.15829 9.54882 9.35355 9.35355C9.54882 9.15829 9.54882 8.84171 9.35355 8.64645L6.70711 6L9.35355 3.35355Z'
fill='currentColor'
></path>
</svg>
<XIcon />
</span>
</span>
<span className='m-1.5 flex items-center justify-center rounded border-[.5px] border-stroke bg-gray py-1.5 px-2.5 text-sm font-medium dark:border-strokedark dark:bg-white/30'>
Development
<span className='cursor-pointer pl-2 hover:text-danger'>
<svg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M9.35355 3.35355C9.54882 3.15829 9.54882 2.84171 9.35355 2.64645C9.15829 2.45118 8.84171 2.45118 8.64645 2.64645L6 5.29289L3.35355 2.64645C3.15829 2.45118 2.84171 2.45118 2.64645 2.64645C2.45118 2.84171 2.45118 3.15829 2.64645 3.35355L5.29289 6L2.64645 8.64645C2.45118 8.84171 2.45118 9.15829 2.64645 9.35355C2.84171 9.54882 3.15829 9.54882 3.35355 9.35355L6 6.70711L8.64645 9.35355C8.84171 9.54882 9.15829 9.54882 9.35355 9.35355C9.54882 9.15829 9.54882 8.84171 9.35355 8.64645L6.70711 6L9.35355 3.35355Z'
fill='currentColor'
></path>
</svg>
<XIcon />
</span>
</span>
</div>
<select name='' id='' className='absolute top-0 left-0 z-20 h-full w-full bg-transparent opacity-0'>
<select
name=''
id=''
className='absolute top-0 left-0 z-20 h-full w-full bg-transparent opacity-0'
>
<option value=''>Option</option>
<option value=''>Option</option>
</select>
<span className='absolute top-1/2 right-4 z-10 -translate-y-1/2'>
<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
<g opacity='0.8'>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M5.29289 8.29289C5.68342 7.90237 6.31658 7.90237 6.70711 8.29289L12 13.5858L17.2929 8.29289C17.6834 7.90237 18.3166 7.90237 18.7071 8.29289C19.0976 8.68342 19.0976 9.31658 18.7071 9.70711L12.7071 15.7071C12.3166 16.0976 11.6834 16.0976 11.2929 15.7071L5.29289 9.70711C4.90237 9.31658 4.90237 8.68342 5.29289 8.29289Z'
fill='#637381'
></path>
</g>
</svg>
<ChevronDownIcon />
</span>
</div>
</div>
@ -272,4 +221,70 @@ const FormElements = ({ user }: { user: AuthUser }) => {
);
};
function SwitchExamples() {
const [isFirstOn, setIsFirstOn] = useState<boolean>(false);
const [isSecondOn, setIsSecondOn] = useState<boolean>(false);
return (
<div className='flex flex-col gap-5.5 p-6.5'>
<SwitcherOne isOn={isFirstOn} onChange={setIsFirstOn} />
<SwitcherTwo isOn={isSecondOn} onChange={setIsSecondOn} />
</div>
);
}
function GlobeIcon() {
return (
<svg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'>
<g opacity='0.8'>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M10.0007 2.50065C5.85852 2.50065 2.50065 5.85852 2.50065 10.0007C2.50065 14.1428 5.85852 17.5007 10.0007 17.5007C14.1428 17.5007 17.5007 14.1428 17.5007 10.0007C17.5007 5.85852 14.1428 2.50065 10.0007 2.50065ZM0.833984 10.0007C0.833984 4.93804 4.93804 0.833984 10.0007 0.833984C15.0633 0.833984 19.1673 4.93804 19.1673 10.0007C19.1673 15.0633 15.0633 19.1673 10.0007 19.1673C4.93804 19.1673 0.833984 15.0633 0.833984 10.0007Z'
fill='#637381'
></path>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M0.833984 9.99935C0.833984 9.53911 1.20708 9.16602 1.66732 9.16602H18.334C18.7942 9.16602 19.1673 9.53911 19.1673 9.99935C19.1673 10.4596 18.7942 10.8327 18.334 10.8327H1.66732C1.20708 10.8327 0.833984 10.4596 0.833984 9.99935Z'
fill='#637381'
></path>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M7.50084 10.0008C7.55796 12.5632 8.4392 15.0301 10.0006 17.0418C11.5621 15.0301 12.4433 12.5632 12.5005 10.0008C12.4433 7.43845 11.5621 4.97153 10.0007 2.95982C8.4392 4.97153 7.55796 7.43845 7.50084 10.0008ZM10.0007 1.66749L9.38536 1.10547C7.16473 3.53658 5.90275 6.69153 5.83417 9.98346C5.83392 9.99503 5.83392 10.0066 5.83417 10.0182C5.90275 13.3101 7.16473 16.4651 9.38536 18.8962C9.54325 19.069 9.76655 19.1675 10.0007 19.1675C10.2348 19.1675 10.4581 19.069 10.6159 18.8962C12.8366 16.4651 14.0986 13.3101 14.1671 10.0182C14.1674 10.0066 14.1674 9.99503 14.1671 9.98346C14.0986 6.69153 12.8366 3.53658 10.6159 1.10547L10.0007 1.66749Z'
fill='#637381'
></path>
</g>
</svg>
);
}
function ChevronDownIcon() {
return (
<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
<g opacity='0.8'>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M5.29289 8.29289C5.68342 7.90237 6.31658 7.90237 6.70711 8.29289L12 13.5858L17.2929 8.29289C17.6834 7.90237 18.3166 7.90237 18.7071 8.29289C19.0976 8.68342 19.0976 9.31658 18.7071 9.70711L12.7071 15.7071C12.3166 16.0976 11.6834 16.0976 11.2929 15.7071L5.29289 9.70711C4.90237 9.31658 4.90237 8.68342 5.29289 8.29289Z'
fill='#637381'
></path>
</g>
</svg>
);
}
function XIcon() {
return (
<svg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M9.35355 3.35355C9.54882 3.15829 9.54882 2.84171 9.35355 2.64645C9.15829 2.45118 8.84171 2.45118 8.64645 2.64645L6 5.29289L3.35355 2.64645C3.15829 2.45118 2.84171 2.45118 2.64645 2.64645C2.45118 2.84171 2.45118 3.15829 2.64645 3.35355L5.29289 6L2.64645 8.64645C2.45118 8.84171 2.45118 9.15829 2.64645 9.35355C2.84171 9.54882 3.15829 9.54882 3.35355 9.35355L6 6.70711L8.64645 9.35355C8.84171 9.54882 9.15829 9.54882 9.35355 9.35355C9.54882 9.15829 9.54882 8.84171 9.35355 8.64645L6.70711 6L9.35355 3.35355Z'
fill='currentColor'
></path>
</svg>
);
}
export default FormElements;

View File

@ -0,0 +1,30 @@
import { useId } from 'react';
import { cn } from '../../../client/cn';
function SwitcherOne({ isOn, onChange }: { isOn: boolean; onChange: (value: boolean) => void }) {
const id = useId();
return (
<div className='relative'>
<label htmlFor={id} className='flex cursor-pointer select-none items-center'>
<div className='relative'>
<input
id={id}
type='checkbox'
className='sr-only'
checked={isOn}
onChange={(e) => onChange(e.target.checked)}
/>
<div className='reblock h-8 w-14 rounded-full bg-meta-9 dark:bg-[#5A616B]'></div>
<div
className={cn('absolute left-1 top-1 h-6 w-6 rounded-full bg-white dark:bg-gray-400 transition', {
'!right-1 !translate-x-full !bg-primary dark:!bg-white': isOn,
})}
></div>
</div>
</label>
</div>
);
}
export default SwitcherOne;

View File

@ -1,66 +1,73 @@
import { useState } from 'react';
import { useId } from 'react';
import { cn } from '../../../client/cn';
const SwitcherTwo = () => {
const [enabled, setEnabled] = useState(false);
function SwitcherTwo({ isOn, onChange }: { isOn: boolean; onChange: (value: boolean) => void }) {
const id = useId();
return (
<div>
<label htmlFor='toggle3' className='flex cursor-pointer select-none items-center'>
<label htmlFor={id} className='flex cursor-pointer select-none items-center'>
<div className='relative'>
<input
type='checkbox'
id='toggle3'
id={id}
className='sr-only'
onChange={() => {
setEnabled(!enabled);
}}
checked={isOn}
onChange={(e) => onChange(e.target.checked)}
/>
<div className='block h-8 w-14 rounded-full bg-meta-9 dark:bg-[#5A616B]'></div>
<div
className={cn(
'dot absolute left-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-white transition',
{
'!right-1 !translate-x-full !bg-primary dark:!bg-white': enabled,
'!right-1 !translate-x-full !bg-primary dark:!bg-white': isOn,
}
)}
>
<span className={cn('hidden', { '!block': enabled })}>
<svg
className='fill-white dark:fill-black'
width='11'
height='8'
viewBox='0 0 11 8'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M10.0915 0.951972L10.0867 0.946075L10.0813 0.940568C9.90076 0.753564 9.61034 0.753146 9.42927 0.939309L4.16201 6.22962L1.58507 3.63469C1.40401 3.44841 1.11351 3.44879 0.932892 3.63584C0.755703 3.81933 0.755703 4.10875 0.932892 4.29224L0.932878 4.29225L0.934851 4.29424L3.58046 6.95832C3.73676 7.11955 3.94983 7.2 4.1473 7.2C4.36196 7.2 4.55963 7.11773 4.71406 6.9584L10.0468 1.60234C10.2436 1.4199 10.2421 1.1339 10.0915 0.951972ZM4.2327 6.30081L4.2317 6.2998C4.23206 6.30015 4.23237 6.30049 4.23269 6.30082L4.2327 6.30081Z'
fill=''
stroke=''
strokeWidth='0.4'
></path>
</svg>
<span className={cn('hidden', { '!block': isOn })}>
<CheckIcon />
</span>
<span
className={cn({
hidden: enabled,
})}
>
<svg
className='h-4 w-4 stroke-current'
fill='none'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
>
<path strokeLinecap='round' strokeLinejoin='round' strokeWidth='2' d='M6 18L18 6M6 6l12 12'></path>
</svg>
<span className={cn({ hidden: isOn })}>
<XIcon />
</span>
</div>
</div>
</label>
</div>
);
}
const XIcon = () => {
return (
<svg
className='h-4 w-4 stroke-current'
fill='none'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
>
<path strokeLinecap='round' strokeLinejoin='round' strokeWidth='2' d='M6 18L18 6M6 6l12 12'></path>
</svg>
);
};
const CheckIcon = () => {
return (
<svg
className='fill-white dark:fill-black'
width='11'
height='8'
viewBox='0 0 11 8'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M10.0915 0.951972L10.0867 0.946075L10.0813 0.940568C9.90076 0.753564 9.61034 0.753146 9.42927 0.939309L4.16201 6.22962L1.58507 3.63469C1.40401 3.44841 1.11351 3.44879 0.932892 3.63584C0.755703 3.81933 0.755703 4.10875 0.932892 4.29224L0.932878 4.29225L0.934851 4.29424L3.58046 6.95832C3.73676 7.11955 3.94983 7.2 4.1473 7.2C4.36196 7.2 4.55963 7.11773 4.71406 6.9584L10.0468 1.60234C10.2436 1.4199 10.2421 1.1339 10.0915 0.951972ZM4.2327 6.30081L4.2317 6.2998C4.23206 6.30015 4.23237 6.30049 4.23269 6.30082L4.2327 6.30081Z'
fill=''
stroke=''
strokeWidth='0.4'
></path>
</svg>
);
};
export default SwitcherTwo;

View File

@ -7,9 +7,7 @@ import { ensureArgsSchemaOrThrowHttpError } from '../server/validation';
const updateUserAdminByIdInputSchema = z.object({
id: z.string().nonempty(),
data: z.object({
isAdmin: z.boolean(),
}),
isAdmin: z.boolean(),
});
type UpdateUserAdminByIdInput = z.infer<typeof updateUserAdminByIdInputSchema>;
@ -18,30 +16,27 @@ export const updateIsUserAdminById: UpdateIsUserAdminById<UpdateUserAdminByIdInp
rawArgs,
context
) => {
const { id, data } = ensureArgsSchemaOrThrowHttpError(updateUserAdminByIdInputSchema, rawArgs);
const { id, isAdmin } = ensureArgsSchemaOrThrowHttpError(updateUserAdminByIdInputSchema, rawArgs);
if (!context.user) {
throw new HttpError(401);
throw new HttpError(401, 'Only authenticated users are allowed to perform this operation');
}
if (!context.user.isAdmin) {
throw new HttpError(403);
throw new HttpError(403, 'Only admins are allowed to perform this operation');
}
const updatedUser = await context.entities.User.update({
where: {
id,
},
data: {
isAdmin: data.isAdmin,
},
return context.entities.User.update({
where: { id },
data: { isAdmin },
});
return updatedUser;
};
type GetPaginatedUsersOutput = {
users: Pick<User, 'id' | 'email' | 'username' | 'subscriptionStatus' | 'paymentProcessorUserId'>[];
users: Pick<
User,
'id' | 'email' | 'username' | 'subscriptionStatus' | 'paymentProcessorUserId' | 'isAdmin'
>[];
totalPages: number;
};
@ -59,7 +54,7 @@ export const getPaginatedUsers: GetPaginatedUsers<GetPaginatedUsersInput, GetPag
rawArgs,
context
) => {
const { skip, cursor, emailContains, isAdmin, subscriptionStatus } = ensureArgsSchemaOrThrowHttpError(
const { skip, emailContains, isAdmin, subscriptionStatus } = ensureArgsSchemaOrThrowHttpError(
getPaginatorArgsSchema,
rawArgs
);
@ -70,7 +65,7 @@ export const getPaginatedUsers: GetPaginatedUsers<GetPaginatedUsersInput, GetPag
const allSubscriptionStatusOptions = subscriptionStatus;
const hasNotSubscribed = allSubscriptionStatusOptions?.find((status) => status === null);
let subscriptionStatusStrings = allSubscriptionStatusOptions?.filter((status) => status !== null) as
const subscriptionStatusStrings = allSubscriptionStatusOptions?.filter((status) => status !== null) as
| string[]
| undefined;