mirror of
https://github.com/snachodog/tok-to-insta-follower-bridge.git
synced 2025-04-10 14:11:22 -06:00
refactor: usercard
This commit is contained in:
parent
4229a39aae
commit
4383913ed5
112
src/lib/components/DetectedUserListItem.stories.tsx
Normal file
112
src/lib/components/DetectedUserListItem.stories.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
|
import { ACTION_MODE, BSKY_USER_MATCH_TYPE } from "../constants";
|
||||||
|
import DetectedUserListItem, { type Props } from "./DetectedUserListItem";
|
||||||
|
|
||||||
|
const meta: Meta<typeof DetectedUserListItem> = {
|
||||||
|
title: "Components/DetectedUserListItem",
|
||||||
|
component: DetectedUserListItem,
|
||||||
|
};
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<{
|
||||||
|
items: {
|
||||||
|
user: Props["user"];
|
||||||
|
action: Props["clickAction"];
|
||||||
|
}[];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const demoUser: Props["user"] = {
|
||||||
|
did: "",
|
||||||
|
handle: "kawamataryo.bsky.social",
|
||||||
|
displayName: "KawamataRyo",
|
||||||
|
description: `
|
||||||
|
Frontend engineer @lapras-inc/ TypeScript / Vue.js / Firebase / ex-FireFighter 🔥
|
||||||
|
Developer of Sky Follower Bridge.
|
||||||
|
|
||||||
|
Twitter: twitter.com/KawamataRyo
|
||||||
|
GitHub: github.com/kawamataryo
|
||||||
|
Zenn: zenn.dev/ryo_kawamata`,
|
||||||
|
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 () => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
};
|
||||||
|
|
||||||
|
const CardTemplate = {
|
||||||
|
render: (args: Story["args"]["items"][0]) => (
|
||||||
|
<DetectedUserListItem
|
||||||
|
user={args.user}
|
||||||
|
clickAction={args.action}
|
||||||
|
actionMode={ACTION_MODE.FOLLOW}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const CardsTemplate: Story = {
|
||||||
|
render: (args) => (
|
||||||
|
<div className="divide-y divide-gray-400 border-y border-gray-400">
|
||||||
|
{args.items.map((arg, i) => (
|
||||||
|
<DetectedUserListItem
|
||||||
|
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
|
||||||
|
key={i}
|
||||||
|
user={arg.user}
|
||||||
|
clickAction={arg.action}
|
||||||
|
actionMode={ACTION_MODE.FOLLOW}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default = {
|
||||||
|
...CardTemplate,
|
||||||
|
args: {
|
||||||
|
action: mockAction,
|
||||||
|
user: {
|
||||||
|
...demoUser,
|
||||||
|
matchType: BSKY_USER_MATCH_TYPE.HANDLE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Cards = {
|
||||||
|
...CardsTemplate,
|
||||||
|
args: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
action: mockAction,
|
||||||
|
user: {
|
||||||
|
...demoUser,
|
||||||
|
matchType: BSKY_USER_MATCH_TYPE.HANDLE,
|
||||||
|
isFollowing: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: mockAction,
|
||||||
|
user: {
|
||||||
|
...demoUser,
|
||||||
|
matchType: BSKY_USER_MATCH_TYPE.DESCRIPTION,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: mockAction,
|
||||||
|
user: {
|
||||||
|
...demoUser,
|
||||||
|
matchType: BSKY_USER_MATCH_TYPE.DISPLAY_NAME,
|
||||||
|
inFollowing: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
99
src/lib/components/DetectedUserListItem.tsx
Normal file
99
src/lib/components/DetectedUserListItem.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { match } from "ts-pattern";
|
||||||
|
import type { BskyUser } from "~types";
|
||||||
|
import { ACTION_MODE } from "../constants";
|
||||||
|
import DetectedUserSource from "./DetectedUserSource";
|
||||||
|
import UserCard from "./UserCard";
|
||||||
|
export type Props = {
|
||||||
|
user: BskyUser;
|
||||||
|
actionMode: (typeof ACTION_MODE)[keyof typeof ACTION_MODE];
|
||||||
|
clickAction: (user: BskyUser) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DetectedUserListItem = ({ user, actionMode, clickAction }: Props) => {
|
||||||
|
const [isBtnHovered, setIsBtnHovered] = React.useState(false);
|
||||||
|
const [isJustClicked, setIsJustClicked] = React.useState(false);
|
||||||
|
const actionBtnLabelAndClass = React.useMemo(
|
||||||
|
() =>
|
||||||
|
match(actionMode)
|
||||||
|
.with(ACTION_MODE.FOLLOW, ACTION_MODE.IMPORT_LIST, () => {
|
||||||
|
const follow = {
|
||||||
|
label: "Follow on Bluesky",
|
||||||
|
class: "btn-primary",
|
||||||
|
};
|
||||||
|
const following = {
|
||||||
|
label: "Following on Bluesky",
|
||||||
|
class:
|
||||||
|
"btn-outline hover:bg-transparent hover:border hover:bg-transparent hover:text-base-content",
|
||||||
|
};
|
||||||
|
const unfollow = {
|
||||||
|
label: "Unfollow on Bluesky",
|
||||||
|
class:
|
||||||
|
"text-red-500 hover:bg-transparent hover:border hover:border-red-500",
|
||||||
|
};
|
||||||
|
if (!isBtnHovered) {
|
||||||
|
return user.isFollowing ? following : follow;
|
||||||
|
}
|
||||||
|
if (user.isFollowing) {
|
||||||
|
return isJustClicked ? following : unfollow;
|
||||||
|
}
|
||||||
|
return follow;
|
||||||
|
})
|
||||||
|
.with(ACTION_MODE.BLOCK, () => {
|
||||||
|
const block = {
|
||||||
|
label: "Block on Bluesky",
|
||||||
|
class: "btn-primary",
|
||||||
|
};
|
||||||
|
const blocking = {
|
||||||
|
label: "Blocking on Bluesky",
|
||||||
|
class:
|
||||||
|
"btn-outline hover:bg-transparent hover:border hover:bg-transparent hover:text-base-content",
|
||||||
|
};
|
||||||
|
const unblock = {
|
||||||
|
label: "Unblock on Bluesky",
|
||||||
|
class:
|
||||||
|
"text-red-500 hover:bg-transparent hover:border hover:border-red-500",
|
||||||
|
};
|
||||||
|
if (!isBtnHovered) {
|
||||||
|
return user.isBlocking ? blocking : block;
|
||||||
|
}
|
||||||
|
if (user.isBlocking) {
|
||||||
|
return isJustClicked ? blocking : unblock;
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
})
|
||||||
|
.run(),
|
||||||
|
[
|
||||||
|
user.isFollowing,
|
||||||
|
user.isBlocking,
|
||||||
|
actionMode,
|
||||||
|
isBtnHovered,
|
||||||
|
isJustClicked,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
|
||||||
|
const handleActionButtonClick = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
await clickAction(user);
|
||||||
|
setLoading(false);
|
||||||
|
setIsJustClicked(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-base-100 w-full relative grid grid-cols-[22%_1fr] gap-5">
|
||||||
|
<DetectedUserSource user={user} />
|
||||||
|
<UserCard
|
||||||
|
user={user}
|
||||||
|
loading={loading}
|
||||||
|
actionBtnLabelAndClass={actionBtnLabelAndClass}
|
||||||
|
handleActionButtonClick={handleActionButtonClick}
|
||||||
|
setIsBtnHovered={setIsBtnHovered}
|
||||||
|
setIsJustClicked={setIsJustClicked}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetectedUserListItem;
|
50
src/lib/components/DetectedUserSource.tsx
Normal file
50
src/lib/components/DetectedUserSource.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import React from "react";
|
||||||
|
import type { BskyUser } from "~types";
|
||||||
|
import { MATCH_TYPE_LABEL_AND_COLOR } from "../constants";
|
||||||
|
import { UserInfo, UserProfile } from "./UserCard";
|
||||||
|
|
||||||
|
type DetectedUserSourceProps = {
|
||||||
|
user: BskyUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DetectedUserSource = ({ user }: DetectedUserSourceProps) => (
|
||||||
|
<div className="flex flex-row gap-2 bg-slate-100 dark:bg-slate-800 justify-between pr-2">
|
||||||
|
<div
|
||||||
|
className={`border-l-8 border-${
|
||||||
|
MATCH_TYPE_LABEL_AND_COLOR[user.matchType].color
|
||||||
|
} relative py-3 pl-4 pr-1 grid grid-cols-[50px_1fr]`}
|
||||||
|
>
|
||||||
|
<UserProfile
|
||||||
|
avatar={user.originalAvatar}
|
||||||
|
url={user.originalProfileLink}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex justify-between items-center gap-2">
|
||||||
|
<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="m8.25 4.5 7.5 7.5-7.5 7.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default DetectedUserSource;
|
@ -1,7 +1,7 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
import { ACTION_MODE, BSKY_USER_MATCH_TYPE } from "../constants";
|
import { BSKY_USER_MATCH_TYPE } from "../constants";
|
||||||
import UserCard, { type Props } from "./UserCard";
|
import UserCard, { type UserCardProps } from "./UserCard";
|
||||||
|
|
||||||
const meta: Meta<typeof UserCard> = {
|
const meta: Meta<typeof UserCard> = {
|
||||||
title: "Components/UserCard",
|
title: "Components/UserCard",
|
||||||
@ -11,12 +11,11 @@ export default meta;
|
|||||||
|
|
||||||
type Story = StoryObj<{
|
type Story = StoryObj<{
|
||||||
items: {
|
items: {
|
||||||
user: Props["user"];
|
user: UserCardProps["user"];
|
||||||
action: Props["clickAction"];
|
|
||||||
}[];
|
}[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const demoUser: Props["user"] = {
|
const demoUser: UserCardProps["user"] = {
|
||||||
did: "",
|
did: "",
|
||||||
handle: "kawamataryo.bsky.social",
|
handle: "kawamataryo.bsky.social",
|
||||||
displayName: "KawamataRyo",
|
displayName: "KawamataRyo",
|
||||||
@ -38,17 +37,15 @@ const demoUser: Props["user"] = {
|
|||||||
originalDisplayName: "KawamataRyo",
|
originalDisplayName: "KawamataRyo",
|
||||||
originalProfileLink: "https://x.com/kawamataryo",
|
originalProfileLink: "https://x.com/kawamataryo",
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockAction: Props["clickAction"] = async () => {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
||||||
};
|
|
||||||
|
|
||||||
const CardTemplate = {
|
const CardTemplate = {
|
||||||
render: (args: Story["args"]["items"][0]) => (
|
render: (args: Story["args"]["items"][0]) => (
|
||||||
<UserCard
|
<UserCard
|
||||||
user={args.user}
|
user={args.user}
|
||||||
clickAction={args.action}
|
loading={false}
|
||||||
actionMode={ACTION_MODE.FOLLOW}
|
actionBtnLabelAndClass={{ label: "Follow", class: "btn-primary" }}
|
||||||
|
handleActionButtonClick={() => {}}
|
||||||
|
setIsBtnHovered={() => {}}
|
||||||
|
setIsJustClicked={() => {}}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@ -61,8 +58,11 @@ const CardsTemplate: Story = {
|
|||||||
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
|
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
|
||||||
key={i}
|
key={i}
|
||||||
user={arg.user}
|
user={arg.user}
|
||||||
clickAction={arg.action}
|
loading={false}
|
||||||
actionMode={ACTION_MODE.FOLLOW}
|
actionBtnLabelAndClass={{ label: "Follow", class: "btn-primary" }}
|
||||||
|
handleActionButtonClick={() => {}}
|
||||||
|
setIsBtnHovered={() => {}}
|
||||||
|
setIsJustClicked={() => {}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -72,41 +72,9 @@ const CardsTemplate: Story = {
|
|||||||
export const Default = {
|
export const Default = {
|
||||||
...CardTemplate,
|
...CardTemplate,
|
||||||
args: {
|
args: {
|
||||||
action: mockAction,
|
|
||||||
user: {
|
user: {
|
||||||
...demoUser,
|
...demoUser,
|
||||||
matchType: BSKY_USER_MATCH_TYPE.HANDLE,
|
matchType: BSKY_USER_MATCH_TYPE.HANDLE,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Cards = {
|
|
||||||
...CardsTemplate,
|
|
||||||
args: {
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
action: mockAction,
|
|
||||||
user: {
|
|
||||||
...demoUser,
|
|
||||||
matchType: BSKY_USER_MATCH_TYPE.HANDLE,
|
|
||||||
isFollowing: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: mockAction,
|
|
||||||
user: {
|
|
||||||
...demoUser,
|
|
||||||
matchType: BSKY_USER_MATCH_TYPE.DESCRIPTION,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: mockAction,
|
|
||||||
user: {
|
|
||||||
...demoUser,
|
|
||||||
matchType: BSKY_USER_MATCH_TYPE.DISPLAY_NAME,
|
|
||||||
inFollowing: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { match } from "ts-pattern";
|
|
||||||
import type { BskyUser } from "~types";
|
import type { BskyUser } from "~types";
|
||||||
import { ACTION_MODE, MATCH_TYPE_LABEL_AND_COLOR } from "../constants";
|
|
||||||
import AvatarFallbackSvg from "./Icons/AvatarFallbackSvg";
|
import AvatarFallbackSvg from "./Icons/AvatarFallbackSvg";
|
||||||
|
|
||||||
type UserProfileProps = {
|
type UserProfileProps = {
|
||||||
@ -9,7 +7,7 @@ type UserProfileProps = {
|
|||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserProfile = ({ avatar, url }: UserProfileProps) => (
|
export const UserProfile = ({ avatar, url }: UserProfileProps) => (
|
||||||
<div className="avatar">
|
<div className="avatar">
|
||||||
<div className="w-10 h-10 rounded-full border border-white">
|
<div className="w-10 h-10 rounded-full border border-white">
|
||||||
<a href={url} target="_blank" rel="noreferrer">
|
<a href={url} target="_blank" rel="noreferrer">
|
||||||
@ -25,7 +23,7 @@ type UserInfoProps = {
|
|||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserInfo = ({ handle, displayName, url }: UserInfoProps) => (
|
export const UserInfo = ({ handle, displayName, url }: UserInfoProps) => (
|
||||||
<div>
|
<div>
|
||||||
<h2 className="card-title break-all text-[1.1rem] font-bold">
|
<h2 className="card-title break-all text-[1.1rem] font-bold">
|
||||||
<a href={url} target="_blank" rel="noreferrer">
|
<a href={url} target="_blank" rel="noreferrer">
|
||||||
@ -48,7 +46,7 @@ type ActionButtonProps = {
|
|||||||
setIsJustClicked: (value: boolean) => void;
|
setIsJustClicked: (value: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ActionButton = ({
|
export const ActionButton = ({
|
||||||
loading,
|
loading,
|
||||||
actionBtnLabelAndClass,
|
actionBtnLabelAndClass,
|
||||||
handleActionButtonClick,
|
handleActionButtonClick,
|
||||||
@ -71,150 +69,48 @@ const ActionButton = ({
|
|||||||
{loading ? "Processing..." : actionBtnLabelAndClass.label}
|
{loading ? "Processing..." : actionBtnLabelAndClass.label}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
export type UserCardProps = {
|
||||||
export type Props = {
|
|
||||||
user: BskyUser;
|
user: BskyUser;
|
||||||
actionMode: (typeof ACTION_MODE)[keyof typeof ACTION_MODE];
|
loading: boolean;
|
||||||
clickAction: (user: BskyUser) => Promise<void>;
|
actionBtnLabelAndClass: { label: string; class: string };
|
||||||
|
handleActionButtonClick: () => void;
|
||||||
|
setIsBtnHovered: (value: boolean) => void;
|
||||||
|
setIsJustClicked: (value: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserCard = ({ user, actionMode, clickAction }: Props) => {
|
const UserCard = ({
|
||||||
const [isBtnHovered, setIsBtnHovered] = React.useState(false);
|
user,
|
||||||
const [isJustClicked, setIsJustClicked] = React.useState(false);
|
loading,
|
||||||
const actionBtnLabelAndClass = React.useMemo(
|
actionBtnLabelAndClass,
|
||||||
() =>
|
handleActionButtonClick,
|
||||||
match(actionMode)
|
setIsBtnHovered,
|
||||||
.with(ACTION_MODE.FOLLOW, ACTION_MODE.IMPORT_LIST, () => {
|
setIsJustClicked,
|
||||||
const follow = {
|
}: UserCardProps) => (
|
||||||
label: "Follow on Bluesky",
|
<div className="relative py-3 pl-0 pr-2 grid grid-cols-[50px_1fr]">
|
||||||
class: "btn-primary",
|
<UserProfile
|
||||||
};
|
avatar={user.avatar}
|
||||||
const following = {
|
url={`https://bsky.app/profile/${user.handle}`}
|
||||||
label: "Following on Bluesky",
|
/>
|
||||||
class:
|
<div className="flex flex-col gap-2">
|
||||||
"btn-outline hover:bg-transparent hover:border hover:bg-transparent hover:text-base-content",
|
<div className="flex justify-between items-center gap-2">
|
||||||
};
|
<UserInfo
|
||||||
const unfollow = {
|
handle={user.handle}
|
||||||
label: "Unfollow on Bluesky",
|
displayName={user.displayName}
|
||||||
class:
|
|
||||||
"text-red-500 hover:bg-transparent hover:border hover:border-red-500",
|
|
||||||
};
|
|
||||||
if (!isBtnHovered) {
|
|
||||||
return user.isFollowing ? following : follow;
|
|
||||||
}
|
|
||||||
if (user.isFollowing) {
|
|
||||||
return isJustClicked ? following : unfollow;
|
|
||||||
}
|
|
||||||
return follow;
|
|
||||||
})
|
|
||||||
.with(ACTION_MODE.BLOCK, () => {
|
|
||||||
const block = {
|
|
||||||
label: "Block on Bluesky",
|
|
||||||
class: "btn-primary",
|
|
||||||
};
|
|
||||||
const blocking = {
|
|
||||||
label: "Blocking on Bluesky",
|
|
||||||
class:
|
|
||||||
"btn-outline hover:bg-transparent hover:border hover:bg-transparent hover:text-base-content",
|
|
||||||
};
|
|
||||||
const unblock = {
|
|
||||||
label: "Unblock on Bluesky",
|
|
||||||
class:
|
|
||||||
"text-red-500 hover:bg-transparent hover:border hover:border-red-500",
|
|
||||||
};
|
|
||||||
if (!isBtnHovered) {
|
|
||||||
return user.isBlocking ? blocking : block;
|
|
||||||
}
|
|
||||||
if (user.isBlocking) {
|
|
||||||
return isJustClicked ? blocking : unblock;
|
|
||||||
}
|
|
||||||
return block;
|
|
||||||
})
|
|
||||||
.run(),
|
|
||||||
[
|
|
||||||
user.isFollowing,
|
|
||||||
user.isBlocking,
|
|
||||||
actionMode,
|
|
||||||
isBtnHovered,
|
|
||||||
isJustClicked,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [loading, setLoading] = React.useState(false);
|
|
||||||
|
|
||||||
const handleActionButtonClick = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
await clickAction(user);
|
|
||||||
setLoading(false);
|
|
||||||
setIsJustClicked(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="bg-base-100 w-full relative grid grid-cols-[22%_1fr] gap-5">
|
|
||||||
<div className="flex flex-row gap-2 bg-slate-100 dark:bg-slate-800 justify-between pr-2">
|
|
||||||
<div
|
|
||||||
className={`border-l-8 border-${
|
|
||||||
MATCH_TYPE_LABEL_AND_COLOR[user.matchType].color
|
|
||||||
} relative py-3 pl-4 pr-1 grid grid-cols-[50px_1fr]`}
|
|
||||||
>
|
|
||||||
<UserProfile
|
|
||||||
avatar={user.originalAvatar}
|
|
||||||
url={user.originalProfileLink}
|
|
||||||
/>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div className="flex justify-between items-center gap-2">
|
|
||||||
<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="m8.25 4.5 7.5 7.5-7.5 7.5"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="relative py-3 pl-0 pr-2 grid grid-cols-[50px_1fr]">
|
|
||||||
<UserProfile
|
|
||||||
avatar={user.avatar}
|
|
||||||
url={`https://bsky.app/profile/${user.handle}`}
|
url={`https://bsky.app/profile/${user.handle}`}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="card-actions">
|
||||||
<div className="flex justify-between items-center gap-2">
|
<ActionButton
|
||||||
<UserInfo
|
loading={loading}
|
||||||
handle={user.handle}
|
actionBtnLabelAndClass={actionBtnLabelAndClass}
|
||||||
displayName={user.displayName}
|
handleActionButtonClick={handleActionButtonClick}
|
||||||
url={`https://bsky.app/profile/${user.handle}`}
|
setIsBtnHovered={setIsBtnHovered}
|
||||||
/>
|
setIsJustClicked={setIsJustClicked}
|
||||||
<div className="card-actions">
|
/>
|
||||||
<ActionButton
|
|
||||||
loading={loading}
|
|
||||||
actionBtnLabelAndClass={actionBtnLabelAndClass}
|
|
||||||
handleActionButtonClick={handleActionButtonClick}
|
|
||||||
setIsBtnHovered={setIsBtnHovered}
|
|
||||||
setIsJustClicked={setIsJustClicked}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm break-all">{user.description}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-sm break-all">{user.description}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
};
|
);
|
||||||
|
|
||||||
export default UserCard;
|
export default UserCard;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import UserCard from "~lib/components/UserCard";
|
|
||||||
import { useBskyUserManager } from "~lib/hooks/useBskyUserManager";
|
import { useBskyUserManager } from "~lib/hooks/useBskyUserManager";
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
import { ToastContainer, toast } from "react-toastify";
|
import { ToastContainer, toast } from "react-toastify";
|
||||||
import useConfirm from "~lib/components/ConfirmDialog";
|
import useConfirm from "~lib/components/ConfirmDialog";
|
||||||
import Sidebar from "~lib/components/Sidebar";
|
import Sidebar from "~lib/components/Sidebar";
|
||||||
import "react-toastify/dist/ReactToastify.css";
|
import "react-toastify/dist/ReactToastify.css";
|
||||||
|
import DetectedUserListItem from "~lib/components/DetectedUserListItem";
|
||||||
|
|
||||||
const Option = () => {
|
const Option = () => {
|
||||||
const {
|
const {
|
||||||
@ -121,7 +121,7 @@ const Option = () => {
|
|||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="divide-y divide-gray-500">
|
<div className="divide-y divide-gray-500">
|
||||||
{filteredUsers.map((user) => (
|
{filteredUsers.map((user) => (
|
||||||
<UserCard
|
<DetectedUserListItem
|
||||||
key={user.handle}
|
key={user.handle}
|
||||||
user={user}
|
user={user}
|
||||||
clickAction={handleClickAction}
|
clickAction={handleClickAction}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user