add card deletion
This commit is contained in:
parent
619c8bf2cd
commit
18271a7d69
@ -12,9 +12,9 @@ import { getGlobalConfig } from '@/config';
|
|||||||
import { UserPayload } from '@/types/user';
|
import { UserPayload } from '@/types/user';
|
||||||
import { DB } from '@/types/db';
|
import { DB } from '@/types/db';
|
||||||
|
|
||||||
export const getCards = async (user: UserPayload) => {
|
export const getCards = async (user: number) => {
|
||||||
return db.selectFrom('aime_card')
|
return db.selectFrom('aime_card')
|
||||||
.where('user', '=', user.id)
|
.where('user', '=', user)
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.execute();
|
.execute();
|
||||||
};
|
};
|
||||||
@ -65,11 +65,26 @@ export const adminAddCardToUser = async (user: number, code: string): Promise<Ac
|
|||||||
return { data: await getUsers() };
|
return { data: await getUsers() };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const deleteCard = async (user: number, id: number) => {
|
||||||
|
const requestingUser = await requireUser();
|
||||||
|
|
||||||
|
if (requestingUser.id !== user)
|
||||||
|
requirePermission(requestingUser.permissions, UserPermissions.USERMOD);
|
||||||
|
|
||||||
|
await db.deleteFrom('aime_card')
|
||||||
|
.where('id', '=', id)
|
||||||
|
.where('user', '=', user)
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
revalidatePath('/settings', 'page');
|
||||||
|
revalidatePath('/admin/users', 'page');
|
||||||
|
};
|
||||||
|
|
||||||
export const userAddCard = async (code: string): Promise<ActionResult<{ card: DB['aime_card'] }>> => {
|
export const userAddCard = async (code: string): Promise<ActionResult<{ card: DB['aime_card'] }>> => {
|
||||||
const user = await requireUser();
|
const user = await requireUser();
|
||||||
|
|
||||||
if (!hasPermission(user.permissions, UserPermissions.USERMOD)) {
|
if (!hasPermission(user.permissions, UserPermissions.USERMOD)) {
|
||||||
const cards = await getCards(user);
|
const cards = await getCards(user.id);
|
||||||
|
|
||||||
if (!getGlobalConfig('allow_user_add_card'))
|
if (!getGlobalConfig('allow_user_add_card'))
|
||||||
return { error: true, message: 'You do not have permissions to add a card' };
|
return { error: true, message: 'You do not have permissions to add a card' };
|
||||||
|
@ -12,7 +12,7 @@ import { hasPermission } from '@/helpers/permissions';
|
|||||||
import { AimeCard } from '@/components/aime-card';
|
import { AimeCard } from '@/components/aime-card';
|
||||||
import { useErrorModal } from '@/components/error-modal';
|
import { useErrorModal } from '@/components/error-modal';
|
||||||
import { AdminUser } from '@/data/user';
|
import { AdminUser } from '@/data/user';
|
||||||
import { adminAddCardToUser } from '@/actions/card';
|
import { adminAddCardToUser, deleteCard } from '@/actions/card';
|
||||||
import { TrashIcon } from '@heroicons/react/24/outline';
|
import { TrashIcon } from '@heroicons/react/24/outline';
|
||||||
import { useConfirmModal } from '@/components/confirm-modal';
|
import { useConfirmModal } from '@/components/confirm-modal';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
@ -75,7 +75,7 @@ export const AdminUserList = ({ users: initialUsers }: { users: AdminUser[]; })
|
|||||||
onSelectionChange={s => typeof s !== 'string' && setOpenUsers(s)}
|
onSelectionChange={s => typeof s !== 'string' && setOpenUsers(s)}
|
||||||
className="my-1 border-b sm:border-b-0 border-divider sm:bg-content1 sm:rounded-lg sm:px-4 overflow-hidden">
|
className="my-1 border-b sm:border-b-0 border-divider sm:bg-content1 sm:rounded-lg sm:px-4 overflow-hidden">
|
||||||
|
|
||||||
{users.map(userEntry => (<AccordionItem key={userEntry.uuid}
|
{users.map(userEntry => (<AccordionItem key={userEntry.uuid ?? userEntry.id}
|
||||||
id={userEntry.uuid ?? undefined} indicator={({ isOpen }) => <Tooltip content="Show cards">
|
id={userEntry.uuid ?? undefined} indicator={({ isOpen }) => <Tooltip content="Show cards">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<CreditCardIcon className="h-6 w-6 mr-1" />
|
<CreditCardIcon className="h-6 w-6 mr-1" />
|
||||||
@ -142,6 +142,14 @@ export const AdminUserList = ({ users: initialUsers }: { users: AdminUser[]; })
|
|||||||
<section className="flex sm:p-4">
|
<section className="flex sm:p-4">
|
||||||
<div className="flex-grow flex flex-wrap items-center justify-center gap-2">
|
<div className="flex-grow flex flex-wrap items-center justify-center gap-2">
|
||||||
{userEntry.cards.map(c => <AimeCard key={c.access_code}
|
{userEntry.cards.map(c => <AimeCard key={c.access_code}
|
||||||
|
canDelete
|
||||||
|
onDelete={() => {
|
||||||
|
deleteCard(c.user!, c.id!);
|
||||||
|
setUsers(u => u.map(u => u.id === userEntry.id ? {
|
||||||
|
...u,
|
||||||
|
cards: u.cards.filter(card => card.id !== c.id)
|
||||||
|
} : u));
|
||||||
|
}}
|
||||||
card={{
|
card={{
|
||||||
...c,
|
...c,
|
||||||
created_date: new Date(c.created_date!),
|
created_date: new Date(c.created_date!),
|
||||||
|
@ -11,7 +11,7 @@ import { UserPermissions } from '@/types/permissions';
|
|||||||
import { hasPermission } from '@/helpers/permissions';
|
import { hasPermission } from '@/helpers/permissions';
|
||||||
import { useErrorModal } from '@/components/error-modal';
|
import { useErrorModal } from '@/components/error-modal';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { userAddCard } from '@/actions/card';
|
import { deleteCard, userAddCard } from '@/actions/card';
|
||||||
|
|
||||||
export type CardsProps = {
|
export type CardsProps = {
|
||||||
cards: DB['aime_card'][],
|
cards: DB['aime_card'][],
|
||||||
@ -21,7 +21,7 @@ export type CardsProps = {
|
|||||||
|
|
||||||
export const Cards = ({ cards: initialCards, canAddCard, maxCard }: CardsProps) => {
|
export const Cards = ({ cards: initialCards, canAddCard, maxCard }: CardsProps) => {
|
||||||
const prompt = usePromptModal();
|
const prompt = usePromptModal();
|
||||||
const user = useUser();
|
const user = useUser({ required: true });
|
||||||
const setError = useErrorModal();
|
const setError = useErrorModal();
|
||||||
const [cards, setCards] = useState(initialCards);
|
const [cards, setCards] = useState(initialCards);
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ export const Cards = ({ cards: initialCards, canAddCard, maxCard }: CardsProps)
|
|||||||
<header className="text-2xl font-semibold flex px-4 h-16 items-center">
|
<header className="text-2xl font-semibold flex px-4 h-16 items-center">
|
||||||
Cards
|
Cards
|
||||||
|
|
||||||
{(hasPermission(user?.permissions, UserPermissions.USERMOD) ||
|
{(hasPermission(user.permissions, UserPermissions.USERMOD) ||
|
||||||
(canAddCard && cards.length < (maxCard ?? Infinity))) && <Tooltip content="Add card">
|
(canAddCard && cards.length < (maxCard ?? Infinity))) && <Tooltip content="Add card">
|
||||||
<Button isIconOnly className="ml-auto" onPress={() => {
|
<Button isIconOnly className="ml-auto" onPress={() => {
|
||||||
promptAccessCode(prompt, 'Enter an access code for this card', code => {
|
promptAccessCode(prompt, 'Enter an access code for this card', code => {
|
||||||
@ -47,7 +47,10 @@ export const Cards = ({ cards: initialCards, canAddCard, maxCard }: CardsProps)
|
|||||||
</header>
|
</header>
|
||||||
<Divider className="mb-4 hidden sm:block" />
|
<Divider className="mb-4 hidden sm:block" />
|
||||||
<div className="px-1 sm:px-4 sm:pb-4 flex flex-wrap items-center justify-center gap-4">
|
<div className="px-1 sm:px-4 sm:pb-4 flex flex-wrap items-center justify-center gap-4">
|
||||||
{cards.map(c => <AimeCard key={c.id} card={c} className="w-full" />)}
|
{cards.map(c => <AimeCard canDelete key={c.id} card={c} className="w-full" onDelete={() => {
|
||||||
|
deleteCard(user.id, c.id);
|
||||||
|
setCards(cards => cards.filter(card => card.id !== c.id));
|
||||||
|
}} />)}
|
||||||
</div>
|
</div>
|
||||||
</section>);
|
</section>);
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ import { getGlobalConfig } from '@/config';
|
|||||||
|
|
||||||
export default async function SettingsPage() {
|
export default async function SettingsPage() {
|
||||||
const user = await requireUser();
|
const user = await requireUser();
|
||||||
const cards = await getCards(user);
|
const cards = await getCards(user.id);
|
||||||
|
|
||||||
return (<div className="w-full flex items-center justify-center">
|
return (<div className="w-full flex items-center justify-center">
|
||||||
<div className="w-full max-w-full sm:max-w-5xl flex flex-col gap-2 2xl:max-w-screen-4xl 2xl:grid grid-cols-12">
|
<div className="w-full max-w-full sm:max-w-5xl flex flex-col gap-2 2xl:max-w-screen-4xl 2xl:grid grid-cols-12">
|
||||||
|
@ -5,22 +5,27 @@ import { useState } from 'react';
|
|||||||
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
|
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
|
||||||
import { Button, Tooltip } from '@nextui-org/react';
|
import { Button, Tooltip } from '@nextui-org/react';
|
||||||
import { useUser } from '@/helpers/use-user';
|
import { useUser } from '@/helpers/use-user';
|
||||||
import { TbHammer, TbHammerOff, TbLock, TbLockOpen } from 'react-icons/tb';
|
import { TbHammer, TbHammerOff, TbLock, TbLockOpen, TbTrashX } from 'react-icons/tb';
|
||||||
import { hasPermission } from '@/helpers/permissions';
|
import { hasPermission } from '@/helpers/permissions';
|
||||||
import { UserPermissions } from '@/types/permissions';
|
import { UserPermissions } from '@/types/permissions';
|
||||||
import { banUnbanCard, lockUnlockCard } from '@/actions/card';
|
import { banUnbanCard, lockUnlockCard } from '@/actions/card';
|
||||||
|
import { useConfirmModal } from './confirm-modal';
|
||||||
|
|
||||||
type AimeCardProps = {
|
type AimeCardProps = {
|
||||||
card: DB['aime_card'],
|
card: DB['aime_card'],
|
||||||
className?: string,
|
className?: string,
|
||||||
|
canDelete?: boolean,
|
||||||
|
onDelete?: () => void
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AimeCard = ({ className, card }: AimeCardProps) => {
|
export const AimeCard = ({ className, card, canDelete, onDelete }: AimeCardProps) => {
|
||||||
const [showCode, setShowCode] = useState(false);
|
const [showCode, setShowCode] = useState(false);
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
const canBan = hasPermission(user?.permissions, UserPermissions.USERMOD);
|
const canBan = hasPermission(user?.permissions, UserPermissions.USERMOD);
|
||||||
const [locked, setLocked] = useState(!!card.is_locked ?? false);
|
const [locked, setLocked] = useState(!!card.is_locked ?? false);
|
||||||
const [banned, setBanned] = useState(!!card.is_banned ?? true);
|
const [banned, setBanned] = useState(!!card.is_banned ?? true);
|
||||||
|
const confirm = useConfirmModal();
|
||||||
|
|
||||||
const formatOptions = {
|
const formatOptions = {
|
||||||
year: '2-digit',
|
year: '2-digit',
|
||||||
month: 'numeric',
|
month: 'numeric',
|
||||||
@ -73,9 +78,16 @@ export const AimeCard = ({ className, card }: AimeCardProps) => {
|
|||||||
</>}
|
</>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-sm sm:text-medium">
|
<div className="text-sm sm:text-medium flex items-end">
|
||||||
{card.last_login_date ? `Last Used ${card.last_login_date.toLocaleTimeString(undefined, formatOptions)}` :
|
{card.last_login_date ? `Last Used ${card.last_login_date.toLocaleTimeString(undefined, formatOptions)}` :
|
||||||
'Never Used'}
|
'Never Used'}
|
||||||
|
|
||||||
|
{canDelete && <Tooltip content={<span className="text-danger">Delete this card</span>}>
|
||||||
|
<Button className="ml-auto" isIconOnly color="danger" variant="faded" onPress={() => confirm('Do you want to delete this card?',
|
||||||
|
() => onDelete?.())}>
|
||||||
|
<TbTrashX className="w-7 h-7" />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(locked || banned) && <div className="absolute flex items-center justify-center w-full left-0 top-[60%] backdrop-blur h-12 sm:h-16 bg-gray-600/50 font-bold sm:text-2xl">
|
{(locked || banned) && <div className="absolute flex items-center justify-center w-full left-0 top-[60%] backdrop-blur h-12 sm:h-16 bg-gray-600/50 font-bold sm:text-2xl">
|
||||||
|
@ -88,7 +88,8 @@ export const getUsers = async () => {
|
|||||||
] as const)
|
] as const)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
return parseJsonResult(res, ['cards']);
|
const data = parseJsonResult(res, ['cards']);
|
||||||
|
return data.map(d => ({ ...d, cards: d.cards.filter(c => c.id !== null) }));
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AdminUser = Awaited<ReturnType<typeof getUsers>>[number];
|
export type AdminUser = Awaited<ReturnType<typeof getUsers>>[number];
|
||||||
|
Loading…
Reference in New Issue
Block a user