forked from administration/panel
feat: features yeah
parent
bebe115db2
commit
efafed4931
|
@ -33,3 +33,7 @@ yarn-error.log*
|
||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
|
# data
|
||||||
|
exports/**
|
||||||
|
!exports/.gitkeep
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import type { Filter } from "mongodb";
|
||||||
|
import { Message, User } from "revolt-api";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "../ui/card";
|
||||||
|
import { fetchMessages, fetchUsersById } from "@/lib/db";
|
||||||
|
import { CompactMessage } from "../cards/CompactMessage";
|
||||||
|
|
||||||
|
export async function RecentMessages({
|
||||||
|
query,
|
||||||
|
users,
|
||||||
|
}: {
|
||||||
|
query: Filter<Message>;
|
||||||
|
users?: boolean | User[];
|
||||||
|
}) {
|
||||||
|
const recentMessages = await fetchMessages(query);
|
||||||
|
|
||||||
|
const userList = (
|
||||||
|
users === true
|
||||||
|
? await fetchUsersById([...new Set(recentMessages.map((x) => x.author))])
|
||||||
|
: Array.isArray(users)
|
||||||
|
? users
|
||||||
|
: []
|
||||||
|
).reduce((prev, next) => {
|
||||||
|
prev[next._id] = next;
|
||||||
|
return prev;
|
||||||
|
}, {} as Record<string, any>);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Recent Messages</CardTitle>
|
||||||
|
<CardDescription>Overview of recent messages</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{/* enter reason for fetching */}
|
||||||
|
{recentMessages.map((message) => (
|
||||||
|
<CompactMessage
|
||||||
|
key={message._id}
|
||||||
|
message={message}
|
||||||
|
hideUser={Object.keys(userList).length === 0}
|
||||||
|
users={userList}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
|
@ -20,11 +20,19 @@ import {
|
||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
} from "../ui/alert-dialog";
|
} from "../ui/alert-dialog";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
import { sendAlert } from "@/lib/actions";
|
import { banUser, sendAlert, suspendUser } from "@/lib/actions";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
|
import { useToast } from "../ui/use-toast";
|
||||||
|
|
||||||
export function UserActions({ id }: { id: string }) {
|
export function UserActions({
|
||||||
|
id,
|
||||||
|
counts,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
counts: { pending: number; all: number };
|
||||||
|
}) {
|
||||||
const alertMessage = useRef("");
|
const alertMessage = useRef("");
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
@ -34,12 +42,73 @@ export function UserActions({ id }: { id: string }) {
|
||||||
>
|
>
|
||||||
Account
|
Account
|
||||||
</Link>
|
</Link>
|
||||||
<Button className="flex-1 bg-orange-400 hover:bg-orange-300">
|
|
||||||
Suspend
|
<AlertDialog>
|
||||||
</Button>
|
<AlertDialogTrigger asChild>
|
||||||
<Button className="flex-1" variant="destructive">
|
<Button className="flex-1 bg-orange-400 hover:bg-orange-300">
|
||||||
Ban
|
Suspend
|
||||||
</Button>
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>
|
||||||
|
Are you sure you want to suspend this user?
|
||||||
|
</AlertDialogTitle>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={() =>
|
||||||
|
suspendUser(id)
|
||||||
|
.then(() => toast({ title: "Suspended user" }))
|
||||||
|
.catch((err) =>
|
||||||
|
toast({
|
||||||
|
title: "Failed to suspend user!",
|
||||||
|
description: err,
|
||||||
|
variant: "destructive",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Suspend
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button className="flex-1" variant="destructive">
|
||||||
|
Ban
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>
|
||||||
|
Are you sure you want to ban this user?
|
||||||
|
</AlertDialogTitle>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={() =>
|
||||||
|
banUser(id)
|
||||||
|
.then(() => toast({ title: "Banned user" }))
|
||||||
|
.catch((err) =>
|
||||||
|
toast({
|
||||||
|
title: "Failed to ban user!",
|
||||||
|
description: err,
|
||||||
|
variant: "destructive",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Ban
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="outline" className="flex-1">
|
<Button variant="outline" className="flex-1">
|
||||||
|
@ -79,8 +148,17 @@ export function UserActions({ id }: { id: string }) {
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!alertMessage.current) return;
|
if (!alertMessage.current) return;
|
||||||
sendAlert(id, alertMessage.current);
|
|
||||||
alertMessage.current = "";
|
alertMessage.current = "";
|
||||||
|
|
||||||
|
sendAlert(id, alertMessage.current)
|
||||||
|
.then(() => toast({ title: "Sent Alert" }))
|
||||||
|
.catch((err) =>
|
||||||
|
toast({
|
||||||
|
title: "Failed to send alert!",
|
||||||
|
description: err,
|
||||||
|
variant: "destructive",
|
||||||
|
})
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Send
|
Send
|
||||||
|
@ -89,9 +167,13 @@ export function UserActions({ id }: { id: string }) {
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
|
||||||
<DropdownMenuItem>Inspect Messages</DropdownMenuItem>
|
{/* <DropdownMenuItem>
|
||||||
<DropdownMenuItem>Wipe Messages</DropdownMenuItem>
|
Clear ({counts.pending}) Friend Requests
|
||||||
<DropdownMenuItem>Clear Friends</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
<DropdownMenuItem>
|
||||||
|
Clear All ({counts.all}) Relations
|
||||||
|
</DropdownMenuItem> */}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
161
lib/actions.ts
161
lib/actions.ts
|
@ -1,9 +1,26 @@
|
||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
|
import { writeFile } from "fs/promises";
|
||||||
import { PLATFORM_MOD_ID } from "./constants";
|
import { PLATFORM_MOD_ID } from "./constants";
|
||||||
import { createDM, findDM, updateLastMessageId } from "./db";
|
import mongo, {
|
||||||
import { sendChatMessage } from "./redis";
|
createDM,
|
||||||
|
fetchChannels,
|
||||||
|
fetchMembershipsByUser,
|
||||||
|
fetchMessages,
|
||||||
|
fetchUserById,
|
||||||
|
findDM,
|
||||||
|
updateLastMessageId,
|
||||||
|
} from "./db";
|
||||||
|
import { publishMessage, sendChatMessage } from "./redis";
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
|
import {
|
||||||
|
AccountInfo,
|
||||||
|
File,
|
||||||
|
Member,
|
||||||
|
Message,
|
||||||
|
SessionInfo,
|
||||||
|
User,
|
||||||
|
} from "revolt-api";
|
||||||
|
|
||||||
export async function sendAlert(userId: string, content: string) {
|
export async function sendAlert(userId: string, content: string) {
|
||||||
const messageId = ulid();
|
const messageId = ulid();
|
||||||
|
@ -19,3 +36,143 @@ export async function sendAlert(userId: string, content: string) {
|
||||||
content,
|
content,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function disableAccount(userId: string) {
|
||||||
|
await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<AccountInfo>("accounts")
|
||||||
|
.updateOne({ _id: userId }, { $set: { disabled: true } });
|
||||||
|
|
||||||
|
await mongo().db("revolt").collection<SessionInfo>("sessions").deleteMany({
|
||||||
|
user_id: userId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function suspendUser(userId: string) {
|
||||||
|
await disableAccount(userId);
|
||||||
|
|
||||||
|
await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<User>("users")
|
||||||
|
.updateOne(
|
||||||
|
{
|
||||||
|
_id: userId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
flags: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const memberships = await fetchMembershipsByUser(userId);
|
||||||
|
|
||||||
|
for (const topic of memberships.map((x) => x._id.server)) {
|
||||||
|
await publishMessage(topic, {
|
||||||
|
type: "UserUpdate",
|
||||||
|
id: userId,
|
||||||
|
data: {
|
||||||
|
flags: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function wipeUser(userId: string, flags = 4) {
|
||||||
|
// retrieve messages, dm channels, relationships, server memberships
|
||||||
|
const backup = {
|
||||||
|
_event: "wipe",
|
||||||
|
user: await fetchUserById(userId),
|
||||||
|
messages: await fetchMessages({ author: userId }, undefined),
|
||||||
|
dms: await fetchChannels({
|
||||||
|
channel_type: "DirectMessage",
|
||||||
|
recipients: userId,
|
||||||
|
}),
|
||||||
|
memberships: await fetchMembershipsByUser(userId),
|
||||||
|
};
|
||||||
|
|
||||||
|
await writeFile(
|
||||||
|
`./exports/${new Date().toISOString()} - ${userId}.json`,
|
||||||
|
JSON.stringify(backup)
|
||||||
|
);
|
||||||
|
|
||||||
|
// mark all attachments as deleted + reported
|
||||||
|
const attachmentIds = backup.messages
|
||||||
|
.filter((message) => message.attachments)
|
||||||
|
.map((message) => message.attachments)
|
||||||
|
.flat()
|
||||||
|
.filter((attachment) => attachment)
|
||||||
|
.map((attachment) => attachment!._id);
|
||||||
|
|
||||||
|
if (backup.user?.avatar) {
|
||||||
|
attachmentIds.push(backup.user.avatar._id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backup.user?.profile?.background) {
|
||||||
|
attachmentIds.push(backup.user.profile.background._id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachmentIds.length) {
|
||||||
|
await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<File>("attachments")
|
||||||
|
.updateMany(
|
||||||
|
{ _id: { $in: attachmentIds } },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
reported: true,
|
||||||
|
deleted: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete messages
|
||||||
|
await mongo().db("revolt").collection<Message>("messages").deleteMany({
|
||||||
|
author: userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// delete server memberships
|
||||||
|
await mongo().db("revolt").collection<Member>("server_members").deleteMany({
|
||||||
|
"_id.user": userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// disable account
|
||||||
|
await disableAccount(userId);
|
||||||
|
|
||||||
|
// clear user profile
|
||||||
|
await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<User>("users")
|
||||||
|
.updateOne(
|
||||||
|
{
|
||||||
|
_id: userId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
flags,
|
||||||
|
},
|
||||||
|
$unset: {
|
||||||
|
avatar: 1,
|
||||||
|
profile: 1,
|
||||||
|
status: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// broadcast wipe event
|
||||||
|
for (const topic of [
|
||||||
|
...backup.dms.map((x) => x._id),
|
||||||
|
...backup.memberships.map((x) => x._id.server),
|
||||||
|
]) {
|
||||||
|
await publishMessage(topic, {
|
||||||
|
type: "UserPlatformWipe",
|
||||||
|
user_id: userId,
|
||||||
|
flags,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function banUser(userId: string) {
|
||||||
|
return await wipeUser(userId, 4);
|
||||||
|
}
|
||||||
|
|
12
lib/db.ts
12
lib/db.ts
|
@ -48,6 +48,14 @@ export async function fetchChannelById(id: string) {
|
||||||
.findOne({ _id: id });
|
.findOne({ _id: id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchChannels(query: Filter<Channel>) {
|
||||||
|
return await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Channel>("channels")
|
||||||
|
.find(query)
|
||||||
|
.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateLastMessageId(
|
export async function updateLastMessageId(
|
||||||
channelId: string,
|
channelId: string,
|
||||||
messageId: string
|
messageId: string
|
||||||
|
@ -122,11 +130,11 @@ export async function fetchMessageById(id: string) {
|
||||||
.findOne({ _id: id });
|
.findOne({ _id: id });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchMessages(query: Filter<Message>) {
|
export async function fetchMessages(query: Filter<Message>, limit = 50) {
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
.collection<Message>("messages")
|
.collection<Message>("messages")
|
||||||
.find(query, { sort: { _id: -1 } })
|
.find(query, { sort: { _id: -1 }, limit })
|
||||||
.toArray();
|
.toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
"@radix-ui/react-popover": "^1.0.6",
|
"@radix-ui/react-popover": "^1.0.6",
|
||||||
"@radix-ui/react-separator": "^1.0.3",
|
"@radix-ui/react-separator": "^1.0.3",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
|
"@radix-ui/react-toast": "^1.1.4",
|
||||||
"@types/node": "20.4.4",
|
"@types/node": "20.4.4",
|
||||||
"@types/react": "18.2.15",
|
"@types/react": "18.2.15",
|
||||||
"@types/react-dom": "18.2.7",
|
"@types/react-dom": "18.2.7",
|
||||||
|
|
|
@ -22,6 +22,9 @@ dependencies:
|
||||||
'@radix-ui/react-slot':
|
'@radix-ui/react-slot':
|
||||||
specifier: ^1.0.2
|
specifier: ^1.0.2
|
||||||
version: 1.0.2(@types/react@18.2.15)(react@18.2.0)
|
version: 1.0.2(@types/react@18.2.15)(react@18.2.0)
|
||||||
|
'@radix-ui/react-toast':
|
||||||
|
specifier: ^1.1.4
|
||||||
|
version: 1.1.4(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: 20.4.4
|
specifier: 20.4.4
|
||||||
version: 20.4.4
|
version: 20.4.4
|
||||||
|
@ -913,6 +916,38 @@ packages:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-toast@1.1.4(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-wf+fc8DOywrpRK3jlPlWVe+ELYGHdKDaaARJZNuUTWyWYq7+ANCFLp4rTjZ/mcGkJJQ/vZ949Zis9xxEpfq9OA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.22.6
|
||||||
|
'@radix-ui/primitive': 1.0.1
|
||||||
|
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.15)(react@18.2.0)
|
||||||
|
'@radix-ui/react-context': 1.0.1(@types/react@18.2.15)(react@18.2.0)
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-portal': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.15)(react@18.2.0)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.15)(react@18.2.0)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.15)(react@18.2.0)
|
||||||
|
'@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@types/react': 18.2.15
|
||||||
|
'@types/react-dom': 18.2.7
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.15)(react@18.2.0):
|
/@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.15)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==}
|
resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1001,6 +1036,27 @@ packages:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.22.6
|
||||||
|
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@types/react': 18.2.15
|
||||||
|
'@types/react-dom': 18.2.7
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/rect@1.0.1:
|
/@radix-ui/rect@1.0.1:
|
||||||
resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==}
|
resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Reference in New Issue