chore: call ban/unban user by authClient.admin
This commit is contained in:
parent
d889cdf2b7
commit
78681df65f
@ -1,47 +0,0 @@
|
||||
'use server';
|
||||
|
||||
import db from '@/db';
|
||||
import { user } from '@/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { createSafeActionClient } from 'next-safe-action';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Create a safe action client
|
||||
const actionClient = createSafeActionClient();
|
||||
|
||||
// Ban user schema for validation
|
||||
const banUserSchema = z.object({
|
||||
userId: z.string().min(1, { message: 'User ID is required' }),
|
||||
reason: z.string().optional(),
|
||||
expiresAt: z.date().nullable(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Ban a user
|
||||
*/
|
||||
export const banUserAction = actionClient
|
||||
.schema(banUserSchema)
|
||||
.action(async ({ parsedInput }) => {
|
||||
try {
|
||||
const { userId, reason, expiresAt } = parsedInput;
|
||||
|
||||
await db
|
||||
.update(user)
|
||||
.set({
|
||||
banned: true,
|
||||
banReason: reason,
|
||||
banExpires: expiresAt,
|
||||
})
|
||||
.where(eq(user.id, userId));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Ban user error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to ban user',
|
||||
};
|
||||
}
|
||||
});
|
@ -1,45 +0,0 @@
|
||||
'use server';
|
||||
|
||||
import db from '@/db';
|
||||
import { user } from '@/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { createSafeActionClient } from 'next-safe-action';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Create a safe action client
|
||||
const actionClient = createSafeActionClient();
|
||||
|
||||
// Unban user schema for validation
|
||||
const unbanUserSchema = z.object({
|
||||
userId: z.string().min(1, { message: 'User ID is required' }),
|
||||
});
|
||||
|
||||
/**
|
||||
* Unban a user
|
||||
*/
|
||||
export const unbanUserAction = actionClient
|
||||
.schema(unbanUserSchema)
|
||||
.action(async ({ parsedInput }) => {
|
||||
try {
|
||||
const { userId } = parsedInput;
|
||||
|
||||
await db
|
||||
.update(user)
|
||||
.set({
|
||||
banned: false,
|
||||
banReason: null,
|
||||
banExpires: null,
|
||||
})
|
||||
.where(eq(user.id, userId));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Unban user error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to unban user',
|
||||
};
|
||||
}
|
||||
});
|
@ -16,6 +16,7 @@ import { Label } from '@/components/ui/label';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { formatDate } from '@/lib/formatter';
|
||||
import {
|
||||
Loader2Icon,
|
||||
@ -25,46 +26,19 @@ import {
|
||||
UserRoundXIcon,
|
||||
} from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import type { SafeActionResult } from 'next-safe-action';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
emailVerified: boolean;
|
||||
image: string | null;
|
||||
role: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
customerId: string | null;
|
||||
banned: boolean | null;
|
||||
banReason: string | null;
|
||||
banExpires: Date | null;
|
||||
}
|
||||
import type { User } from './users-table';
|
||||
|
||||
interface UserDetailViewerProps {
|
||||
user: User;
|
||||
onBan: (
|
||||
userId: string,
|
||||
reason: string,
|
||||
expiresAt: Date | null
|
||||
) => Promise<SafeActionResult<string, any, any, any, any>>;
|
||||
onUnban: (
|
||||
userId: string
|
||||
) => Promise<SafeActionResult<string, any, any, any, any>>;
|
||||
}
|
||||
|
||||
export function UserDetailViewer({
|
||||
user,
|
||||
onBan,
|
||||
onUnban,
|
||||
}: UserDetailViewerProps) {
|
||||
export function UserDetailViewer({ user }: UserDetailViewerProps) {
|
||||
const t = useTranslations('Dashboard.admin.users');
|
||||
const isMobile = useIsMobile();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | undefined>('');
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
const [banReason, setBanReason] = useState('');
|
||||
const [banExpiresAt, setBanExpiresAt] = useState<string>('');
|
||||
|
||||
@ -74,70 +48,58 @@ export function UserDetailViewer({
|
||||
return;
|
||||
}
|
||||
|
||||
if (!user.id) {
|
||||
setError('User ID is required');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const expiresAt = banExpiresAt ? new Date(banExpiresAt) : null;
|
||||
const result = await onBan(user.id, banReason, expiresAt);
|
||||
await authClient.admin.banUser({
|
||||
userId: user.id,
|
||||
banReason,
|
||||
banExpiresIn: expiresAt
|
||||
? Math.floor((expiresAt.getTime() - Date.now()) / 1000)
|
||||
: undefined,
|
||||
});
|
||||
|
||||
if (
|
||||
result?.data &&
|
||||
typeof result.data === 'object' &&
|
||||
'success' in result.data &&
|
||||
result.data.success
|
||||
) {
|
||||
toast.success(t('ban.success'));
|
||||
// Reset form
|
||||
setBanReason('');
|
||||
setBanExpiresAt('');
|
||||
} else {
|
||||
const errorMessage =
|
||||
result?.data &&
|
||||
typeof result.data === 'object' &&
|
||||
'error' in result.data
|
||||
? String(result.data.error)
|
||||
: t('ban.error');
|
||||
toast.error(errorMessage);
|
||||
setError(errorMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('ban user error:', error);
|
||||
setError(t('ban.error'));
|
||||
toast.error(t('ban.error'));
|
||||
toast.success(t('ban.success'));
|
||||
// Reset form
|
||||
setBanReason('');
|
||||
setBanExpiresAt('');
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
console.error('Failed to ban user:', error);
|
||||
setError(error.message || t('ban.error'));
|
||||
toast.error(error.message || t('ban.error'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnban = async () => {
|
||||
if (!user.id) {
|
||||
setError('User ID is required');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const result = await onUnban(user.id);
|
||||
await authClient.admin.unbanUser({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (
|
||||
result?.data &&
|
||||
typeof result.data === 'object' &&
|
||||
'success' in result.data &&
|
||||
result.data.success
|
||||
) {
|
||||
toast.success(t('unban.success'));
|
||||
} else {
|
||||
const errorMessage =
|
||||
result?.data &&
|
||||
typeof result.data === 'object' &&
|
||||
'error' in result.data
|
||||
? String(result.data.error)
|
||||
: t('unban.error');
|
||||
toast.error(errorMessage);
|
||||
setError(errorMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('unban user error:', error);
|
||||
setError(t('unban.error'));
|
||||
toast.error(t('unban.error'));
|
||||
toast.success(t('unban.success'));
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
console.error('Failed to unban user:', error);
|
||||
setError(error.message || t('unban.error'));
|
||||
toast.error(error.message || t('unban.error'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@ -246,7 +208,7 @@ export function UserDetailViewer({
|
||||
variant="destructive"
|
||||
onClick={handleUnban}
|
||||
disabled={isLoading}
|
||||
className="mt-4"
|
||||
className="mt-4 cursor-pointer"
|
||||
>
|
||||
{isLoading && (
|
||||
<Loader2Icon className="mr-2 size-4 animate-spin" />
|
||||
@ -285,7 +247,7 @@ export function UserDetailViewer({
|
||||
type="submit"
|
||||
variant="destructive"
|
||||
disabled={isLoading || !banReason}
|
||||
className="mt-4"
|
||||
className="mt-4 cursor-pointer"
|
||||
>
|
||||
{isLoading && (
|
||||
<Loader2Icon className="mr-2 size-4 animate-spin" />
|
||||
|
@ -1,14 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { banUserAction } from '@/actions/ban-user';
|
||||
import { unbanUserAction } from '@/actions/unban-user';
|
||||
import { UserDetailViewer } from '@/components/admin/user-detail-viewer';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@ -50,7 +47,6 @@ import {
|
||||
ChevronsRightIcon,
|
||||
MailCheckIcon,
|
||||
MailQuestionIcon,
|
||||
MoreVerticalIcon,
|
||||
UserRoundCheckIcon,
|
||||
UserRoundXIcon,
|
||||
} from 'lucide-react';
|
||||
@ -111,41 +107,7 @@ const columns: ColumnDef<User>[] = [
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const user = row.original;
|
||||
return (
|
||||
<UserDetailViewer
|
||||
user={user}
|
||||
onBan={async (userId, reason, expiresAt) => {
|
||||
const result = await banUserAction({ userId, reason, expiresAt });
|
||||
if (!result) {
|
||||
throw new Error('Failed to ban user');
|
||||
}
|
||||
if (result.validationErrors) {
|
||||
throw new Error(
|
||||
Object.values(result.validationErrors).join(', ')
|
||||
);
|
||||
}
|
||||
if (result.serverError) {
|
||||
throw new Error(result.serverError);
|
||||
}
|
||||
return result;
|
||||
}}
|
||||
onUnban={async (userId) => {
|
||||
const result = await unbanUserAction({ userId });
|
||||
if (!result) {
|
||||
throw new Error('Failed to unban user');
|
||||
}
|
||||
if (result.validationErrors) {
|
||||
throw new Error(
|
||||
Object.values(result.validationErrors).join(', ')
|
||||
);
|
||||
}
|
||||
if (result.serverError) {
|
||||
throw new Error(result.serverError);
|
||||
}
|
||||
return result;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return <UserDetailViewer user={user} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -284,38 +246,6 @@ const columns: ColumnDef<User>[] = [
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Actions',
|
||||
cell: ({ row }) => {
|
||||
const user = row.original;
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="cursor-pointer data-[state=open]:bg-muted text-muted-foreground flex size-8"
|
||||
size="icon"
|
||||
>
|
||||
<MoreVerticalIcon />
|
||||
<span className="sr-only">Open menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-32">
|
||||
{user.banned ? (
|
||||
<DropdownMenuItem className="cursor-pointer">
|
||||
Unban User
|
||||
</DropdownMenuItem>
|
||||
) : (
|
||||
<DropdownMenuItem className="cursor-pointer">
|
||||
Ban User
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
interface UsersTableProps {
|
||||
|
Loading…
Reference in New Issue
Block a user