Compare commits
2 Commits
d694040bf7
...
2cf1224107
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cf1224107 | |||
| 7867db425e |
@ -19,11 +19,14 @@ export const ContactButton = ({ product }: { product: ProductDetail }) => {
|
||||
return;
|
||||
}
|
||||
if (!product.seller?.id) return;
|
||||
createChatThread(product.seller.id, {
|
||||
createChatThread(
|
||||
{ participantId: product.seller.id, productId: product.id },
|
||||
{
|
||||
onSuccess: (threadData) => {
|
||||
navigate(`/profile/threads/${threadData.id}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -330,9 +330,12 @@ export const ProductDetailView = memo(function ProductDetailView({
|
||||
return;
|
||||
}
|
||||
if (!product.seller?.id) return;
|
||||
createChatThread(product.seller.id, {
|
||||
createChatThread(
|
||||
{ participantId: product.seller.id, productId: product.id },
|
||||
{
|
||||
onSuccess: (t) => navigate(`/profile/threads/${t.id}`),
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -24,11 +24,14 @@ export const ProductHeader = ({ product }: ProductHeaderProps) => {
|
||||
return;
|
||||
}
|
||||
if (!product.seller?.id) return;
|
||||
createChatThread(product.seller.id, {
|
||||
createChatThread(
|
||||
{ participantId: product.seller.id, productId: product.id },
|
||||
{
|
||||
onSuccess: (threadData) => {
|
||||
navigate(`/profile/threads/${threadData.id}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -20,11 +20,6 @@ interface SizeChartSectionProps {
|
||||
showError?: boolean;
|
||||
}
|
||||
|
||||
const UNITS: { value: string; label: string }[] = [
|
||||
{ value: "cm", label: "سانتیمتر" },
|
||||
{ value: "inch", label: "اینچ" },
|
||||
];
|
||||
|
||||
export function SizeChartSection({
|
||||
value,
|
||||
onChange,
|
||||
@ -171,26 +166,8 @@ export function SizeChartSection({
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Unit toggle */}
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-R12 text-GRAY">واحد اندازهگیری</span>
|
||||
<div className="flex overflow-hidden rounded-lg border border-GRAY3">
|
||||
{UNITS.map((u) => (
|
||||
<button
|
||||
key={u.value}
|
||||
type="button"
|
||||
onClick={() => emit({ unit: u.value })}
|
||||
className={cn(
|
||||
"px-3 py-1 text-R12 transition-colors cursor-pointer",
|
||||
unit === u.value
|
||||
? "bg-BLACK text-WHITE"
|
||||
: "bg-WHITE text-BLACK2 hover:bg-WHITE3"
|
||||
)}
|
||||
>
|
||||
{u.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center justify-end">
|
||||
<span className="text-R12 text-GRAY">اندازهها به سانتیمتر</span>
|
||||
</div>
|
||||
|
||||
{/* Grid */}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Reply, MoreVertical, Trash2, Check } from "lucide-react";
|
||||
import { Link } from "@remix-run/react";
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
@ -81,7 +82,34 @@ export const MessageBubble = ({
|
||||
{replyToMessageContent || "پاسخ به پیام"}
|
||||
</div>
|
||||
)}
|
||||
<p>{message.content}</p>
|
||||
{message.product && (
|
||||
<Link
|
||||
to={`/product/${message.product.id}`}
|
||||
className="flex items-center gap-2 mb-1 w-[210px] max-w-full rounded-lg bg-WHITE border border-GRAY3 p-2"
|
||||
>
|
||||
{message.product.primaryImage ? (
|
||||
<img
|
||||
src={message.product.primaryImage}
|
||||
alt=""
|
||||
className="w-12 h-12 rounded-md object-cover shrink-0"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-12 h-12 rounded-md bg-WHITE3 shrink-0" />
|
||||
)}
|
||||
<div className="min-w-0 text-right">
|
||||
<p className="text-R12 text-BLACK truncate">
|
||||
{message.product.title}
|
||||
</p>
|
||||
{message.product.basePrice && (
|
||||
<p className="text-R10 text-GRAY mt-0.5">
|
||||
{parseInt(message.product.basePrice).toLocaleString("fa-IR")}{" "}
|
||||
تومان
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
{message.content && <p>{message.content}</p>}
|
||||
</div>
|
||||
<div className="flex flex-col justify-between items-start">
|
||||
<div className="flex flex-col gap-1 items-start">
|
||||
|
||||
@ -156,7 +156,10 @@ const ThreadsList = ({
|
||||
<p
|
||||
className={`text-R14 ${hasUnread ? "text-BLACK font-bold" : "text-GRAY"}`}
|
||||
>
|
||||
{thread.lastMessage?.content || "بدون پیام"}
|
||||
{thread.lastMessage?.content ||
|
||||
(thread.lastMessage?.product
|
||||
? `🛍 ${thread.lastMessage.product.title || "محصول"}`
|
||||
: "بدون پیام")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -206,15 +206,21 @@ export function useGetThreadMessages(threadId: string) {
|
||||
export function useCreateChatThread() {
|
||||
const token = useAuthToken();
|
||||
return useMutation({
|
||||
mutationFn: async (participantId: string) => {
|
||||
mutationFn: async (
|
||||
input: string | { participantId: string; productId?: string }
|
||||
) => {
|
||||
if (!token) {
|
||||
throw new Error("Authentication token and user ID are required");
|
||||
}
|
||||
// Back-compat: callers may pass a bare participantId string.
|
||||
const { participantId, productId } =
|
||||
typeof input === "string" ? { participantId: input, productId: undefined } : input;
|
||||
try {
|
||||
const chatApi = createChatApi(token);
|
||||
return await chatApi.apiChatV1ThreadsCreateCreate({
|
||||
data: {
|
||||
participantId: participantId,
|
||||
participantId,
|
||||
...(productId ? { productId } : {}),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@ -2341,6 +2341,43 @@ export interface LastMessage {
|
||||
* @memberof LastMessage
|
||||
*/
|
||||
readonly createdAt?: string;
|
||||
/**
|
||||
* Product shared into the chat, if this message is a product card.
|
||||
* @type {ChatProductCard}
|
||||
* @memberof LastMessage
|
||||
*/
|
||||
readonly product?: ChatProductCard | null;
|
||||
}
|
||||
/**
|
||||
* Minimal product info rendered as a card inside a chat.
|
||||
* @export
|
||||
* @interface ChatProductCard
|
||||
*/
|
||||
export interface ChatProductCard {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ChatProductCard
|
||||
*/
|
||||
readonly id?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ChatProductCard
|
||||
*/
|
||||
readonly title?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ChatProductCard
|
||||
*/
|
||||
readonly basePrice?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ChatProductCard
|
||||
*/
|
||||
readonly primaryImage?: string | null;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -2484,6 +2521,12 @@ export interface MessageList {
|
||||
* @memberof MessageList
|
||||
*/
|
||||
readonly isThreadCreator?: boolean;
|
||||
/**
|
||||
* Product shared into the chat, if this message is a product card.
|
||||
* @type {ChatProductCard}
|
||||
* @memberof MessageList
|
||||
*/
|
||||
readonly product?: ChatProductCard | null;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -6464,6 +6507,12 @@ export interface ThreadCreate {
|
||||
* @memberof ThreadCreate
|
||||
*/
|
||||
participantId: string;
|
||||
/**
|
||||
* Optional product the chat is about; shared into the thread as a card
|
||||
* @type {string}
|
||||
* @memberof ThreadCreate
|
||||
*/
|
||||
productId?: string | null;
|
||||
}
|
||||
/**
|
||||
*
|
||||
|
||||
Loading…
Reference in New Issue
Block a user