mirror of
https://github.com/snachodog/tok-to-insta-follower-bridge.git
synced 2025-04-23 20:12:22 -06:00
refactor: import list
This commit is contained in:
parent
93b4d2f8ae
commit
d354030056
21
src/background/messages/getMyProfile.ts
Normal file
21
src/background/messages/getMyProfile.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import type { PlasmoMessaging } from "@plasmohq/messaging";
|
||||||
|
import { BskyClient } from "~lib/bskyClient";
|
||||||
|
|
||||||
|
const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
|
||||||
|
const { session } = req.body;
|
||||||
|
const client = await BskyClient.createAgentFromSession(session);
|
||||||
|
|
||||||
|
try {
|
||||||
|
res.send({
|
||||||
|
result: await client.getMyProfile(),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
res.send({
|
||||||
|
error: {
|
||||||
|
message: e.message,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default handler;
|
@ -71,12 +71,8 @@ const App = () => {
|
|||||||
sendToBackground({ name: "openOptionPage" });
|
sendToBackground({ name: "openOptionPage" });
|
||||||
};
|
};
|
||||||
|
|
||||||
const stopAndShowDetectedUsers = async () => {
|
const stopAndShowDetectedUsers = () => {
|
||||||
stopRetrieveLoop();
|
stopRetrieveLoop();
|
||||||
await chrome.storage.local.set({
|
|
||||||
users: JSON.stringify(users),
|
|
||||||
listName: listName,
|
|
||||||
});
|
|
||||||
openOptionPage();
|
openOptionPage();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -169,4 +169,12 @@ export class BskyClient {
|
|||||||
await this.addUserToList({ userDid, listUri });
|
await this.addUserToList({ userDid, listUri });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public getMyProfile = async () => {
|
||||||
|
return {
|
||||||
|
pdsUrl: this.agent.pdsUrl,
|
||||||
|
did: this.agent.session.did,
|
||||||
|
handle: this.agent.session.handle,
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -164,5 +164,19 @@ export class BskyServiceWorkerClient {
|
|||||||
for (const userDid of userDids) {
|
for (const userDid of userDids) {
|
||||||
await this.addUserToList({ userDid, listUri });
|
await this.addUserToList({ userDid, listUri });
|
||||||
}
|
}
|
||||||
|
const listId = listUri.split("/").pop();
|
||||||
|
return listId;
|
||||||
|
};
|
||||||
|
|
||||||
|
public getMyProfile = async () => {
|
||||||
|
const { result, error } = await sendToBackground({
|
||||||
|
name: "getMyProfile",
|
||||||
|
body: {
|
||||||
|
session: this.session,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (error) throw new Error(error.message);
|
||||||
|
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,10 @@ import React from "react";
|
|||||||
type Props = {
|
type Props = {
|
||||||
onClick: () => Promise<void>;
|
onClick: () => Promise<void>;
|
||||||
label: string;
|
label: string;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AsyncButton = ({ onClick, label }: Props) => {
|
const AsyncButton = ({ onClick, label, className }: Props) => {
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
|
||||||
const handleClick = async () => {
|
const handleClick = async () => {
|
||||||
@ -17,7 +18,7 @@ const AsyncButton = ({ onClick, label }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-primary btn-wide btn-sm mb-2"
|
className={`btn btn-primary btn-wide btn-sm mb-2 ${className}`}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
|
@ -31,8 +31,14 @@ export const Default: Story = {
|
|||||||
[BSKY_USER_MATCH_TYPE.DESCRIPTION]: 10,
|
[BSKY_USER_MATCH_TYPE.DESCRIPTION]: 10,
|
||||||
[BSKY_USER_MATCH_TYPE.FOLLOWING]: 10,
|
[BSKY_USER_MATCH_TYPE.FOLLOWING]: 10,
|
||||||
},
|
},
|
||||||
actionAll: async () => {
|
importList: async () => {
|
||||||
console.log("actionAll");
|
console.log("importList");
|
||||||
|
},
|
||||||
|
followAll: async () => {
|
||||||
|
console.log("followAll");
|
||||||
|
},
|
||||||
|
blockAll: async () => {
|
||||||
|
console.log("blockAll");
|
||||||
},
|
},
|
||||||
actionMode: ACTION_MODE.FOLLOW,
|
actionMode: ACTION_MODE.FOLLOW,
|
||||||
},
|
},
|
||||||
@ -56,8 +62,14 @@ export const NoDetections: Story = {
|
|||||||
[BSKY_USER_MATCH_TYPE.DESCRIPTION]: 0,
|
[BSKY_USER_MATCH_TYPE.DESCRIPTION]: 0,
|
||||||
[BSKY_USER_MATCH_TYPE.FOLLOWING]: 0,
|
[BSKY_USER_MATCH_TYPE.FOLLOWING]: 0,
|
||||||
},
|
},
|
||||||
actionAll: async () => {
|
importList: async () => {
|
||||||
console.log("actionAll");
|
console.log("importList");
|
||||||
|
},
|
||||||
|
followAll: async () => {
|
||||||
|
console.log("followAll");
|
||||||
|
},
|
||||||
|
blockAll: async () => {
|
||||||
|
console.log("blockAll");
|
||||||
},
|
},
|
||||||
actionMode: ACTION_MODE.FOLLOW,
|
actionMode: ACTION_MODE.FOLLOW,
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { match } from "ts-pattern";
|
||||||
import type { MatchType, MatchTypeFilterValue } from "../../types";
|
import type { MatchType, MatchTypeFilterValue } from "../../types";
|
||||||
import {
|
import {
|
||||||
ACTION_MODE,
|
ACTION_MODE,
|
||||||
@ -12,36 +13,30 @@ type Props = {
|
|||||||
detectedCount: number;
|
detectedCount: number;
|
||||||
filterValue: MatchTypeFilterValue;
|
filterValue: MatchTypeFilterValue;
|
||||||
onChangeFilter: (key: MatchType) => void;
|
onChangeFilter: (key: MatchType) => void;
|
||||||
actionAll: () => Promise<void>;
|
|
||||||
actionMode: (typeof ACTION_MODE)[keyof typeof ACTION_MODE];
|
actionMode: (typeof ACTION_MODE)[keyof typeof ACTION_MODE];
|
||||||
matchTypeStats: Record<Exclude<MatchType, "none">, number>;
|
matchTypeStats: Record<Exclude<MatchType, "none">, number>;
|
||||||
|
importList: () => Promise<void>;
|
||||||
|
followAll: () => Promise<void>;
|
||||||
|
blockAll: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Sidebar = ({
|
const Sidebar = ({
|
||||||
detectedCount,
|
detectedCount,
|
||||||
filterValue,
|
filterValue,
|
||||||
onChangeFilter,
|
onChangeFilter,
|
||||||
actionAll,
|
|
||||||
actionMode,
|
actionMode,
|
||||||
matchTypeStats,
|
matchTypeStats,
|
||||||
|
importList,
|
||||||
|
followAll,
|
||||||
|
blockAll,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const getActionLabel = () => {
|
|
||||||
switch (actionMode) {
|
|
||||||
case ACTION_MODE.FOLLOW:
|
|
||||||
return "Follow All";
|
|
||||||
case ACTION_MODE.BLOCK:
|
|
||||||
return "Block All";
|
|
||||||
case ACTION_MODE.IMPORT_LIST:
|
|
||||||
return "Import List";
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="bg-base-300 w-80 min-h-screen p-4 border-r border-base-300 flex flex-col">
|
<aside className="bg-base-300 w-80 min-h-screen p-4 border-r border-base-300 flex flex-col">
|
||||||
<div className="flex-grow">
|
<div className="flex-grow">
|
||||||
<div className="flex items-center gap-2">
|
<a
|
||||||
|
href="https://sky-follower-bridge.de"
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
className="w-5 h-5"
|
className="w-5 h-5"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -63,7 +58,7 @@ const Sidebar = ({
|
|||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<span className="text-2xl font-bold">Sky Follower Bridge</span>
|
<span className="text-2xl font-bold">Sky Follower Bridge</span>
|
||||||
</div>
|
</a>
|
||||||
<div className="divider" />
|
<div className="divider" />
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<svg
|
<svg
|
||||||
@ -157,7 +152,17 @@ const Sidebar = ({
|
|||||||
</svg>
|
</svg>
|
||||||
<p className="text-xl font-bold">Action</p>
|
<p className="text-xl font-bold">Action</p>
|
||||||
</div>
|
</div>
|
||||||
<AsyncButton onClick={actionAll} label={getActionLabel()} />
|
{match(actionMode)
|
||||||
|
.with(ACTION_MODE.FOLLOW, () => (
|
||||||
|
<AsyncButton onClick={followAll} label="Follow All" />
|
||||||
|
))
|
||||||
|
.with(ACTION_MODE.BLOCK, () => (
|
||||||
|
<AsyncButton onClick={blockAll} label="Block All" />
|
||||||
|
))
|
||||||
|
.with(ACTION_MODE.IMPORT_LIST, () => (
|
||||||
|
<AsyncButton onClick={importList} label="Import List" />
|
||||||
|
))
|
||||||
|
.otherwise(() => null)}
|
||||||
<p className="text-xs">
|
<p className="text-xs">
|
||||||
⚠️ User detection is not perfect and may include false positives.
|
⚠️ User detection is not perfect and may include false positives.
|
||||||
</p>
|
</p>
|
||||||
|
@ -38,6 +38,7 @@ export const STORAGE_KEYS = {
|
|||||||
BSKY_CLIENT_SESSION: `${STORAGE_PREFIX}_bsky_client_session`,
|
BSKY_CLIENT_SESSION: `${STORAGE_PREFIX}_bsky_client_session`,
|
||||||
BSKY_MESSAGE_NAME: `${STORAGE_PREFIX}_bsky_message_name`,
|
BSKY_MESSAGE_NAME: `${STORAGE_PREFIX}_bsky_message_name`,
|
||||||
DETECTED_BSKY_USERS: `${STORAGE_PREFIX}_detected_bsky_users`,
|
DETECTED_BSKY_USERS: `${STORAGE_PREFIX}_detected_bsky_users`,
|
||||||
|
LIST_NAME: `${STORAGE_PREFIX}_list_name`,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const TARGET_URLS_REGEX = {
|
export const TARGET_URLS_REGEX = {
|
||||||
|
@ -125,20 +125,23 @@ export const useBskyUserManager = () => {
|
|||||||
});
|
});
|
||||||
}, [users, matchTypeFilter, actionMode]);
|
}, [users, matchTypeFilter, actionMode]);
|
||||||
|
|
||||||
const actionAll = React.useCallback(async () => {
|
// Import list
|
||||||
|
const importList = React.useCallback(async () => {
|
||||||
|
if (!bskyClient.current) return;
|
||||||
|
const listUri = await bskyClient.current.createListAndAddUsers({
|
||||||
|
name: listName,
|
||||||
|
description: "List imported via Sky Follower Bridge",
|
||||||
|
userDids: filteredUsers.map((user) => user.did),
|
||||||
|
});
|
||||||
|
const myProfile = await bskyClient.current.getMyProfile();
|
||||||
|
return `https://bsky.app/profile/${myProfile.handle}/lists/${listUri}`;
|
||||||
|
}, [filteredUsers, listName]);
|
||||||
|
|
||||||
|
// Follow All
|
||||||
|
const followAll = React.useCallback(async () => {
|
||||||
if (!bskyClient.current) return;
|
if (!bskyClient.current) return;
|
||||||
let actionCount = 0;
|
let actionCount = 0;
|
||||||
|
|
||||||
if (actionMode === ACTION_MODE.IMPORT_LIST) {
|
|
||||||
const userDids = filteredUsers.map((user) => user.did);
|
|
||||||
await bskyClient.current.createListAndAddUsers({
|
|
||||||
name: listName,
|
|
||||||
description: "List imported via Sky Follower Bridge",
|
|
||||||
userDids,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const user of filteredUsers) {
|
for (const user of filteredUsers) {
|
||||||
let resultUri: string | null = null;
|
let resultUri: string | null = null;
|
||||||
// follow
|
// follow
|
||||||
@ -163,8 +166,17 @@ export const useBskyUserManager = () => {
|
|||||||
await wait(300);
|
await wait(300);
|
||||||
actionCount++;
|
actionCount++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return actionCount;
|
||||||
|
}, [filteredUsers, actionMode, setUsers]);
|
||||||
|
|
||||||
// block
|
// Block All
|
||||||
|
const blockAll = React.useCallback(async () => {
|
||||||
|
if (!bskyClient.current) return;
|
||||||
|
// block
|
||||||
|
let actionCount = 0;
|
||||||
|
for (const user of filteredUsers) {
|
||||||
|
let resultUri: string | null = null;
|
||||||
if (actionMode === ACTION_MODE.BLOCK) {
|
if (actionMode === ACTION_MODE.BLOCK) {
|
||||||
if (user.isBlocking) {
|
if (user.isBlocking) {
|
||||||
continue;
|
continue;
|
||||||
@ -188,7 +200,7 @@ export const useBskyUserManager = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return actionCount;
|
return actionCount;
|
||||||
}, [filteredUsers, actionMode, setUsers, listName]);
|
}, [filteredUsers, actionMode, setUsers]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
chrome.storage.local.get(
|
chrome.storage.local.get(
|
||||||
@ -227,7 +239,9 @@ export const useBskyUserManager = () => {
|
|||||||
matchTypeFilter,
|
matchTypeFilter,
|
||||||
changeMatchTypeFilter,
|
changeMatchTypeFilter,
|
||||||
filteredUsers,
|
filteredUsers,
|
||||||
actionAll,
|
|
||||||
matchTypeStats,
|
matchTypeStats,
|
||||||
|
importList,
|
||||||
|
followAll,
|
||||||
|
blockAll,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -46,6 +46,15 @@ export const useRetrieveBskyUsers = () => {
|
|||||||
},
|
},
|
||||||
(v) => (v === undefined ? [] : v),
|
(v) => (v === undefined ? [] : v),
|
||||||
);
|
);
|
||||||
|
const [listName, setListName] = useStorage<string>(
|
||||||
|
{
|
||||||
|
key: STORAGE_KEYS.LIST_NAME,
|
||||||
|
instance: new Storage({
|
||||||
|
area: "local",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
(v) => (v === undefined ? "" : v),
|
||||||
|
);
|
||||||
const [loading, setLoading] = React.useState(true);
|
const [loading, setLoading] = React.useState(true);
|
||||||
const [errorMessage, setErrorMessage] = React.useState("");
|
const [errorMessage, setErrorMessage] = React.useState("");
|
||||||
const [isBottomReached, setIsBottomReached] = React.useState(false);
|
const [isBottomReached, setIsBottomReached] = React.useState(false);
|
||||||
@ -54,7 +63,6 @@ export const useRetrieveBskyUsers = () => {
|
|||||||
session: AtpSessionData;
|
session: AtpSessionData;
|
||||||
messageName: (typeof MESSAGE_NAMES)[keyof typeof MESSAGE_NAMES];
|
messageName: (typeof MESSAGE_NAMES)[keyof typeof MESSAGE_NAMES];
|
||||||
}>(null);
|
}>(null);
|
||||||
const [listName, setListName] = React.useState<string>("");
|
|
||||||
|
|
||||||
const retrieveBskyUsers = React.useCallback(
|
const retrieveBskyUsers = React.useCallback(
|
||||||
async (usersData: CrawledUserInfo[]) => {
|
async (usersData: CrawledUserInfo[]) => {
|
||||||
@ -161,8 +169,7 @@ export const useRetrieveBskyUsers = () => {
|
|||||||
|
|
||||||
bskyClient.current = new BskyServiceWorkerClient(session);
|
bskyClient.current = new BskyServiceWorkerClient(session);
|
||||||
|
|
||||||
const listName = scrapeListNameFromPage();
|
setListName(scrapeListNameFromPage());
|
||||||
setListName(listName);
|
|
||||||
|
|
||||||
startRetrieveLoop(messageName).catch((e) => {
|
startRetrieveLoop(messageName).catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { MESSAGE_NAMES } from "~lib/constants";
|
import { MESSAGE_NAMES } from "~lib/constants";
|
||||||
import { BSKY_DOMAIN, MESSAGE_NAME_TO_QUERY_PARAM_MAP } from "~lib/constants";
|
import { BSKY_DOMAIN } from "~lib/constants";
|
||||||
import { wait } from "~lib/utils";
|
import { wait } from "~lib/utils";
|
||||||
import type { CrawledUserInfo } from "~types";
|
import type { CrawledUserInfo } from "~types";
|
||||||
import { AbstractService } from "./abstractService";
|
import { AbstractService } from "./abstractService";
|
||||||
|
@ -10,16 +10,20 @@ const Option = () => {
|
|||||||
const {
|
const {
|
||||||
users,
|
users,
|
||||||
filteredUsers,
|
filteredUsers,
|
||||||
listName,
|
|
||||||
matchTypeFilter,
|
matchTypeFilter,
|
||||||
changeMatchTypeFilter,
|
changeMatchTypeFilter,
|
||||||
handleClickAction,
|
handleClickAction,
|
||||||
actionMode,
|
actionMode,
|
||||||
actionAll,
|
|
||||||
matchTypeStats,
|
matchTypeStats,
|
||||||
|
importList,
|
||||||
|
followAll,
|
||||||
|
blockAll,
|
||||||
} = useBskyUserManager();
|
} = useBskyUserManager();
|
||||||
|
|
||||||
const { confirm, ConfirmationDialog } = useConfirm({
|
const {
|
||||||
|
confirm: followAllConfirm,
|
||||||
|
ConfirmationDialog: FollowAllConfirmationDialog,
|
||||||
|
} = useConfirm({
|
||||||
title: "Proceed with Execution?",
|
title: "Proceed with Execution?",
|
||||||
message:
|
message:
|
||||||
"User detection is not perfect and may include false positives. Do you still want to proceed?",
|
"User detection is not perfect and may include false positives. Do you still want to proceed?",
|
||||||
@ -27,13 +31,71 @@ const Option = () => {
|
|||||||
okText: "OK",
|
okText: "OK",
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleActionAll = async () => {
|
const {
|
||||||
if (!(await confirm())) {
|
confirm: importListConfirm,
|
||||||
|
ConfirmationDialog: ImportListConfirmationDialog,
|
||||||
|
} = useConfirm({
|
||||||
|
title: "Proceed with Execution?",
|
||||||
|
message:
|
||||||
|
"Importing a list will create a new list and add all detected users to it. This feature is experimental and may not work as expected. Do you still want to proceed?",
|
||||||
|
cancelText: "Cancel",
|
||||||
|
okText: "OK",
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleFollowAll = async () => {
|
||||||
|
if (!(await followAllConfirm())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
toast.promise(followAll, {
|
||||||
|
pending: "Processing...",
|
||||||
|
success: {
|
||||||
|
render({ data }) {
|
||||||
|
return <span className="font-bold">Followed {data} users🎉</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const result = await actionAll();
|
const handleBlockAll = async () => {
|
||||||
toast.success(`Followed ${result} users`);
|
if (!(await followAllConfirm())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast.promise(blockAll, {
|
||||||
|
pending: "Processing...",
|
||||||
|
success: {
|
||||||
|
render({ data }) {
|
||||||
|
return <span className="font-bold">Blocked {data} users🎉</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImportList = async () => {
|
||||||
|
if (!(await importListConfirm())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast.promise(importList, {
|
||||||
|
pending: "Processing...",
|
||||||
|
success: {
|
||||||
|
render({ data }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className="font-bold">List imported successfully🎉</span>
|
||||||
|
<br />
|
||||||
|
<a href={data} target="_blank" rel="noreferrer" className="link">
|
||||||
|
View Imported List
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
render({ data }) {
|
||||||
|
console.log(data);
|
||||||
|
return `Failed to import list: ${data}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -44,9 +106,11 @@ const Option = () => {
|
|||||||
detectedCount={users.length}
|
detectedCount={users.length}
|
||||||
filterValue={matchTypeFilter}
|
filterValue={matchTypeFilter}
|
||||||
onChangeFilter={changeMatchTypeFilter}
|
onChangeFilter={changeMatchTypeFilter}
|
||||||
actionAll={handleActionAll}
|
|
||||||
actionMode={actionMode}
|
actionMode={actionMode}
|
||||||
matchTypeStats={matchTypeStats}
|
matchTypeStats={matchTypeStats}
|
||||||
|
importList={handleImportList}
|
||||||
|
followAll={handleFollowAll}
|
||||||
|
blockAll={handleBlockAll}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 ml-80 p-6 pt-0 overflow-y-auto">
|
<div className="flex-1 ml-80 p-6 pt-0 overflow-y-auto">
|
||||||
@ -72,7 +136,8 @@ const Option = () => {
|
|||||||
autoClose={5000}
|
autoClose={5000}
|
||||||
className="text-sm"
|
className="text-sm"
|
||||||
/>
|
/>
|
||||||
<ConfirmationDialog />
|
<FollowAllConfirmationDialog />
|
||||||
|
<ImportListConfirmationDialog />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user