Skip to content

Commit

Permalink
feat: add search again button
Browse files Browse the repository at this point in the history
  • Loading branch information
kawamataryo committed Dec 1, 2024
1 parent 4383913 commit 29cf178
Show file tree
Hide file tree
Showing 14 changed files with 441 additions and 105 deletions.
63 changes: 63 additions & 0 deletions src/components/ReSearchModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { ProfileView } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
import Modal from "~lib/components/Modal";
import UserCardWithoutActionButton from "~lib/components/UserCardWithoutActionButton";

interface ReSearchModalProps {
open: boolean;
onClose: () => void;
reSearchResults: {
sourceDid: string;
users: ProfileView[];
};
handleClickReSearchResult: ({
sourceDid,
user,
}: {
sourceDid: string;
user: ProfileView;
}) => void;
}

const ReSearchModal: React.FC<ReSearchModalProps> = ({
open,
onClose,
reSearchResults,
handleClickReSearchResult,
}) => {
return (
<Modal open={open} width={600} onClose={onClose}>
<h2 className="text-lg font-bold text-center py-2">Search Results</h2>
{reSearchResults.users.length === 0 && (
<div className="text-center flex justify-center items-center flex-col gap-4 mt-5">
<span className="loading loading-spinner loading-lg" />
<div className="text-center flex justify-center items-center text-sm">
Loading...
</div>
</div>
)}
{reSearchResults.users.length > 0 && (
<div className="divide-y divide-gray-500">
{reSearchResults.users.map((user) => (
<UserCardWithoutActionButton
key={user.handle}
onClick={() =>
handleClickReSearchResult({
sourceDid: reSearchResults.sourceDid,
user,
})
}
user={{
avatar: user.avatar,
handle: user.handle,
displayName: user.displayName,
description: user.description,
}}
/>
))}
</div>
)}
</Modal>
);
};

export default ReSearchModal;
8 changes: 7 additions & 1 deletion src/lib/bskyHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ProfileView } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
import { BSKY_USER_MATCH_TYPE } from "./constants";
import { BSKY_PROFILE_LABEL, BSKY_USER_MATCH_TYPE } from "./constants";

type xUserInfo = {
bskyHandleInDescription: string;
Expand Down Expand Up @@ -80,3 +80,9 @@ export const isSimilarUser = (
type: BSKY_USER_MATCH_TYPE.NONE,
};
};

export const isImpersonationUser = (user: ProfileView) => {
return user.labels.some(
(label) => label.val === BSKY_PROFILE_LABEL.IMPERSONATION,
);
};
35 changes: 35 additions & 0 deletions src/lib/components/ActionButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";

type ActionButtonProps = {
loading: boolean;
actionBtnLabelAndClass: { label: string; class: string };
handleActionButtonClick: () => void;
setIsBtnHovered: (value: boolean) => void;
setIsJustClicked: (value: boolean) => void;
};

export 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 default ActionButton;
22 changes: 21 additions & 1 deletion src/lib/components/DetectedUserListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ProfileView } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
import React from "react";
import { match } from "ts-pattern";
import type { BskyUser } from "~types";
Expand All @@ -8,9 +9,19 @@ export type Props = {
user: BskyUser;
actionMode: (typeof ACTION_MODE)[keyof typeof ACTION_MODE];
clickAction: (user: BskyUser) => Promise<void>;
reSearch: (user: {
sourceDid: string;
accountName: string;
displayName: string;
}) => Promise<void>;
};

const DetectedUserListItem = ({ user, actionMode, clickAction }: Props) => {
const DetectedUserListItem = ({
user,
actionMode,
clickAction,
reSearch,
}: Props) => {
const [isBtnHovered, setIsBtnHovered] = React.useState(false);
const [isJustClicked, setIsJustClicked] = React.useState(false);
const actionBtnLabelAndClass = React.useMemo(
Expand Down Expand Up @@ -81,6 +92,14 @@ const DetectedUserListItem = ({ user, actionMode, clickAction }: Props) => {
setIsJustClicked(true);
};

const handleReSearchClick = () => {
reSearch({
sourceDid: user.did,
accountName: user.originalHandle,
displayName: user.originalDisplayName,
});
};

return (
<div className="bg-base-100 w-full relative grid grid-cols-[22%_1fr] gap-5">
<DetectedUserSource user={user} />
Expand All @@ -91,6 +110,7 @@ const DetectedUserListItem = ({ user, actionMode, clickAction }: Props) => {
handleActionButtonClick={handleActionButtonClick}
setIsBtnHovered={setIsBtnHovered}
setIsJustClicked={setIsJustClicked}
handleReSearchClick={handleReSearchClick}
/>
</div>
);
Expand Down
4 changes: 3 additions & 1 deletion src/lib/components/DetectedUserSource.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { ProfileView } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
import React from "react";
import type { BskyUser } from "~types";
import { MATCH_TYPE_LABEL_AND_COLOR } from "../constants";
import { UserInfo, UserProfile } from "./UserCard";
import UserInfo from "./UserInfo";
import UserProfile from "./UserProfile";

type DetectedUserSourceProps = {
user: BskyUser;
Expand Down
8 changes: 6 additions & 2 deletions src/lib/components/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ export type Props = {
children: React.ReactNode;
open: boolean;
onClose?: () => void;
width?: number;
};

const Modal = ({ children, open = false, onClose }: Props) => {
const Modal = ({ children, open = false, onClose, width = 500 }: Props) => {
const anchorRef = useRef<HTMLDialogElement>(null);

useEffect(() => {
Expand All @@ -25,7 +26,10 @@ const Modal = ({ children, open = false, onClose }: Props) => {
return (
<>
<dialog className="modal" ref={anchorRef} open={open}>
<div className="modal-box p-10 bg-base-100 w-[500px] max-w-none text-base-content">
<div
className="modal-box p-10 bg-base-100 max-w-none text-base-content"
style={{ width }}
>
{children}
</div>
<form method="dialog" className="modal-backdrop">
Expand Down
155 changes: 63 additions & 92 deletions src/lib/components/UserCard.tsx
Original file line number Diff line number Diff line change
@@ -1,81 +1,46 @@
import React from "react";
import type { BskyUser } from "~types";
import AvatarFallbackSvg from "./Icons/AvatarFallbackSvg";
import ActionButton from "./ActionButton";
import UserInfo from "./UserInfo";
import UserProfile from "./UserProfile";

type UserProfileProps = {
avatar: string;
url: string;
};

export 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;
};

export 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 = {
export type UserCardProps = {
user: Pick<BskyUser, "avatar" | "handle" | "displayName" | "description">;
loading: boolean;
actionBtnLabelAndClass: { label: string; class: string };
handleActionButtonClick: () => void;
setIsBtnHovered: (value: boolean) => void;
setIsJustClicked: (value: boolean) => void;
handleReSearchClick: () => void;
};

export 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 UserCardProps = {
user: BskyUser;
loading: boolean;
actionBtnLabelAndClass: { label: string; class: string };
handleActionButtonClick: () => void;
setIsBtnHovered: (value: boolean) => void;
setIsJustClicked: (value: boolean) => void;
const ReSearchButton = ({
onClick,
}: {
onClick: () => void;
}) => {
return (
<button
type="button"
className="btn-outline w-7 h-7 border rounded-full flex items-center justify-center"
onClick={onClick}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-4 w-4"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
</button>
);
};

const UserCard = ({
Expand All @@ -85,32 +50,38 @@ const UserCard = ({
handleActionButtonClick,
setIsBtnHovered,
setIsJustClicked,
}: UserCardProps) => (
<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}`}
/>
<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">
<ActionButton
loading={loading}
actionBtnLabelAndClass={actionBtnLabelAndClass}
handleActionButtonClick={handleActionButtonClick}
setIsBtnHovered={setIsBtnHovered}
setIsJustClicked={setIsJustClicked}
/>
handleReSearchClick,
}: UserCardProps) => {
return (
<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}`}
/>
<div className="flex flex-col gap-2">
<div className="flex justify-between items-center gap-2">
<div className="flex items-start gap-4">
<UserInfo
handle={user.handle}
displayName={user.displayName}
url={`https://bsky.app/profile/${user.handle}`}
/>
<ReSearchButton onClick={handleReSearchClick} />
</div>
<div className="card-actions flex items-center gap-4">
<ActionButton
loading={loading}
actionBtnLabelAndClass={actionBtnLabelAndClass}
handleActionButtonClick={handleActionButtonClick}
setIsBtnHovered={setIsBtnHovered}
setIsJustClicked={setIsJustClicked}
/>
</div>
</div>
<p className="text-sm break-all">{user.description}</p>
</div>
<p className="text-sm break-all">{user.description}</p>
</div>
</div>
);
);
};

export default UserCard;
Loading

0 comments on commit 29cf178

Please sign in to comment.