mirror of
https://github.com/snachodog/tok-to-insta-follower-bridge.git
synced 2025-04-10 14:11:22 -06:00
feat: display the source of detection
This commit is contained in:
parent
9364cc3ce0
commit
be53d28b14
@ -27,12 +27,16 @@ const demoUser: Props["user"] = {
|
||||
Twitter: twitter.com/KawamataRyo
|
||||
GitHub: github.com/kawamataryo
|
||||
Zenn: zenn.dev/ryo_kawamata`,
|
||||
avatar: "https://avatar.iran.liara.run/public",
|
||||
avatar: "https://i.pravatar.cc/150?u=123",
|
||||
matchType: BSKY_USER_MATCH_TYPE.HANDLE,
|
||||
isFollowing: false,
|
||||
followingUri: "",
|
||||
isBlocking: false,
|
||||
blockingUri: "",
|
||||
originalAvatar: "https://i.pravatar.cc/150?u=123",
|
||||
originalHandle: "kawamataryo",
|
||||
originalDisplayName: "KawamataRyo",
|
||||
originalProfileLink: "https://x.com/kawamataryo",
|
||||
};
|
||||
|
||||
const mockAction: Props["clickAction"] = async () => {
|
||||
|
@ -4,6 +4,74 @@ import type { BskyUser } from "~types";
|
||||
import { ACTION_MODE, MATCH_TYPE_LABEL_AND_COLOR } from "../constants";
|
||||
import AvatarFallbackSvg from "./Icons/AvatarFallbackSvg";
|
||||
|
||||
type UserProfileProps = {
|
||||
avatar: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
const UserProfile = ({ avatar, url }: UserProfileProps) => (
|
||||
<div className="avatar">
|
||||
<div className="w-10 h-10 rounded-full border border-white">
|
||||
<a href={url} target="_blank" rel="noreferrer">
|
||||
{avatar ? <img src={avatar} alt="" /> : <AvatarFallbackSvg />}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
type UserInfoProps = {
|
||||
handle: string;
|
||||
displayName: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
const UserInfo = ({ handle, displayName, url }: UserInfoProps) => (
|
||||
<div>
|
||||
<h2 className="card-title break-all text-[1.1rem] font-bold">
|
||||
<a href={url} target="_blank" rel="noreferrer">
|
||||
{displayName}
|
||||
</a>
|
||||
</h2>
|
||||
<p className="w-fit break-all text-gray-500 dark:text-gray-400 text-sm">
|
||||
<a href={url} target="_blank" rel="noreferrer" className="break-all">
|
||||
@{handle}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
type ActionButtonProps = {
|
||||
loading: boolean;
|
||||
actionBtnLabelAndClass: { label: string; class: string };
|
||||
handleActionButtonClick: () => void;
|
||||
setIsBtnHovered: (value: boolean) => void;
|
||||
setIsJustClicked: (value: boolean) => void;
|
||||
};
|
||||
|
||||
const ActionButton = ({
|
||||
loading,
|
||||
actionBtnLabelAndClass,
|
||||
handleActionButtonClick,
|
||||
setIsBtnHovered,
|
||||
setIsJustClicked,
|
||||
}: ActionButtonProps) => (
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm rounded-3xl ${
|
||||
loading ? "" : actionBtnLabelAndClass.class
|
||||
}`}
|
||||
onClick={handleActionButtonClick}
|
||||
onMouseEnter={() => setIsBtnHovered(true)}
|
||||
onMouseLeave={() => {
|
||||
setIsBtnHovered(false);
|
||||
setIsJustClicked(false);
|
||||
}}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? "Processing..." : actionBtnLabelAndClass.label}
|
||||
</button>
|
||||
);
|
||||
|
||||
export type Props = {
|
||||
user: BskyUser;
|
||||
actionMode: (typeof ACTION_MODE)[keyof typeof ACTION_MODE];
|
||||
@ -82,67 +150,62 @@ const UserCard = ({ user, actionMode, clickAction }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-base-100 w-full relative">
|
||||
<div className="bg-base-100 w-full relative grid grid-cols-[20%_5%_75%]">
|
||||
<div
|
||||
className={`border-l-8 border-${
|
||||
MATCH_TYPE_LABEL_AND_COLOR[user.matchType].color
|
||||
} card-body relative py-3 px-4 rounded-sm grid grid-cols-[70px_1fr]`}
|
||||
} card-body relative py-3 pl-4 pr-1 rounded-sm grid grid-cols-[50px_1fr]`}
|
||||
>
|
||||
<div>
|
||||
<div className="avatar">
|
||||
<div className="w-14 rounded-full border border-white ">
|
||||
<a
|
||||
href={`https://bsky.app/profile/${user.handle}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{user.avatar ? (
|
||||
<img src={user.avatar} alt="" />
|
||||
) : (
|
||||
<AvatarFallbackSvg />
|
||||
)}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<UserProfile
|
||||
avatar={user.originalAvatar}
|
||||
url={user.originalProfileLink}
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex justify-between items-center gap-2">
|
||||
<div>
|
||||
<h2 className="card-title break-all">
|
||||
<a
|
||||
href={`https://bsky.app/profile/${user.handle}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{user.displayName}
|
||||
</a>
|
||||
</h2>
|
||||
<p className="whitespace-nowrap w-fit break-all text-gray-500 dark:text-gray-400 text-sm">
|
||||
<a
|
||||
href={`https://bsky.app/profile/${user.handle}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
@{user.handle}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<UserInfo
|
||||
handle={user.originalHandle}
|
||||
displayName={user.originalDisplayName}
|
||||
url={user.originalProfileLink}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="h-7 w-7"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="card-body relative py-3 pl-0 pr-2 rounded-sm grid grid-cols-[50px_1fr]">
|
||||
<UserProfile
|
||||
avatar={user.avatar}
|
||||
url={`https://bsky.app/profile/${user.handle}`}
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex justify-between items-center gap-2">
|
||||
<UserInfo
|
||||
handle={user.handle}
|
||||
displayName={user.displayName}
|
||||
url={`https://bsky.app/profile/${user.handle}`}
|
||||
/>
|
||||
<div className="card-actions">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm rounded-3xl ${
|
||||
loading ? "" : actionBtnLabelAndClass.class
|
||||
}`}
|
||||
onClick={handleActionButtonClick}
|
||||
onMouseEnter={() => setIsBtnHovered(true)}
|
||||
onMouseLeave={() => {
|
||||
setIsBtnHovered(false);
|
||||
setIsJustClicked(false);
|
||||
}}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? "Processing..." : actionBtnLabelAndClass.label}
|
||||
</button>
|
||||
<ActionButton
|
||||
loading={loading}
|
||||
actionBtnLabelAndClass={actionBtnLabelAndClass}
|
||||
handleActionButtonClick={handleActionButtonClick}
|
||||
setIsBtnHovered={setIsBtnHovered}
|
||||
setIsJustClicked={setIsJustClicked}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm break-all">{user.description}</p>
|
||||
|
@ -70,6 +70,10 @@ export const useRetrieveBskyUsers = () => {
|
||||
followingUri: searchResult.bskyProfile.viewer?.following,
|
||||
isBlocking: !!searchResult.bskyProfile.viewer?.blocking,
|
||||
blockingUri: searchResult.bskyProfile.viewer?.blocking,
|
||||
originalAvatar: userData.originalAvatar,
|
||||
originalHandle: userData.accountName,
|
||||
originalDisplayName: userData.displayName,
|
||||
originalProfileLink: userData.originalProfileLink,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
@ -20,6 +20,10 @@ export class XService extends AbstractService {
|
||||
?.match(/bsky\.app\/profile\/([^/\s]+)…?/)?.[1]
|
||||
?.replace("…", "") ??
|
||||
"";
|
||||
const originalAvatar = userCell
|
||||
.querySelector('[data-testid^="UserAvatar-Container"]')
|
||||
?.querySelector("img")
|
||||
?.getAttribute("src");
|
||||
|
||||
return {
|
||||
accountName,
|
||||
@ -27,6 +31,8 @@ export class XService extends AbstractService {
|
||||
accountNameRemoveUnderscore,
|
||||
accountNameReplaceUnderscore,
|
||||
bskyHandle,
|
||||
originalAvatar,
|
||||
originalProfileLink: `https://x.com/${accountName}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -48,19 +48,21 @@ const Option = () => {
|
||||
matchTypeStats={matchTypeStats}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 ml-80 p-6 overflow-y-auto">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="divide-y divide-gray-500">
|
||||
{filteredUsers.map((user) => (
|
||||
<UserCard
|
||||
key={user.handle}
|
||||
user={user}
|
||||
clickAction={handleClickAction}
|
||||
actionMode={actionMode}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex-1 ml-80 p-6 pt-0 overflow-y-auto">
|
||||
<div className="grid grid-cols-[25%_75%] sticky top-0 z-10 bg-base-100 border-b-[1px] border-gray-500">
|
||||
<h2 className="text-lg font-bold text-center py-2">Source</h2>
|
||||
<h2 className="text-lg font-bold text-center py-2">Detected</h2>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="divide-y divide-gray-500">
|
||||
{filteredUsers.map((user) => (
|
||||
<UserCard
|
||||
key={user.handle}
|
||||
user={user}
|
||||
clickAction={handleClickAction}
|
||||
actionMode={actionMode}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -16,6 +16,10 @@ export type BskyUser = {
|
||||
followingUri: string | null;
|
||||
isBlocking: boolean;
|
||||
blockingUri: string | null;
|
||||
originalAvatar: string;
|
||||
originalHandle: string;
|
||||
originalDisplayName: string;
|
||||
originalProfileLink: string;
|
||||
};
|
||||
|
||||
export type MatchTypeFilterValue = {
|
||||
@ -31,4 +35,6 @@ export type CrawledUserInfo = {
|
||||
accountNameRemoveUnderscore: string;
|
||||
accountNameReplaceUnderscore: string;
|
||||
bskyHandle: string;
|
||||
originalAvatar: string;
|
||||
originalProfileLink: string;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user