Compare commits
2 Commits
d694040bf7
...
2cf1224107
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cf1224107 | |||
| 7867db425e |
@ -19,11 +19,14 @@ export const ContactButton = ({ product }: { product: ProductDetail }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!product.seller?.id) return;
|
if (!product.seller?.id) return;
|
||||||
createChatThread(product.seller.id, {
|
createChatThread(
|
||||||
onSuccess: (threadData) => {
|
{ participantId: product.seller.id, productId: product.id },
|
||||||
navigate(`/profile/threads/${threadData.id}`);
|
{
|
||||||
},
|
onSuccess: (threadData) => {
|
||||||
});
|
navigate(`/profile/threads/${threadData.id}`);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -330,9 +330,12 @@ export const ProductDetailView = memo(function ProductDetailView({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!product.seller?.id) return;
|
if (!product.seller?.id) return;
|
||||||
createChatThread(product.seller.id, {
|
createChatThread(
|
||||||
onSuccess: (t) => navigate(`/profile/threads/${t.id}`),
|
{ participantId: product.seller.id, productId: product.id },
|
||||||
});
|
{
|
||||||
|
onSuccess: (t) => navigate(`/profile/threads/${t.id}`),
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -24,11 +24,14 @@ export const ProductHeader = ({ product }: ProductHeaderProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!product.seller?.id) return;
|
if (!product.seller?.id) return;
|
||||||
createChatThread(product.seller.id, {
|
createChatThread(
|
||||||
onSuccess: (threadData) => {
|
{ participantId: product.seller.id, productId: product.id },
|
||||||
navigate(`/profile/threads/${threadData.id}`);
|
{
|
||||||
},
|
onSuccess: (threadData) => {
|
||||||
});
|
navigate(`/profile/threads/${threadData.id}`);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -20,11 +20,6 @@ interface SizeChartSectionProps {
|
|||||||
showError?: boolean;
|
showError?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UNITS: { value: string; label: string }[] = [
|
|
||||||
{ value: "cm", label: "سانتیمتر" },
|
|
||||||
{ value: "inch", label: "اینچ" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export function SizeChartSection({
|
export function SizeChartSection({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
@ -171,26 +166,8 @@ export function SizeChartSection({
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Unit toggle */}
|
<div className="flex items-center justify-end">
|
||||||
<div className="flex items-center justify-between">
|
<span className="text-R12 text-GRAY">اندازهها به سانتیمتر</span>
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
{/* Grid */}
|
{/* Grid */}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Reply, MoreVertical, Trash2, Check } from "lucide-react";
|
import { Reply, MoreVertical, Trash2, Check } from "lucide-react";
|
||||||
|
import { Link } from "@remix-run/react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@ -81,7 +82,34 @@ export const MessageBubble = ({
|
|||||||
{replyToMessageContent || "پاسخ به پیام"}
|
{replyToMessageContent || "پاسخ به پیام"}
|
||||||
</div>
|
</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>
|
||||||
<div className="flex flex-col justify-between items-start">
|
<div className="flex flex-col justify-between items-start">
|
||||||
<div className="flex flex-col gap-1 items-start">
|
<div className="flex flex-col gap-1 items-start">
|
||||||
|
|||||||
@ -156,7 +156,10 @@ const ThreadsList = ({
|
|||||||
<p
|
<p
|
||||||
className={`text-R14 ${hasUnread ? "text-BLACK font-bold" : "text-GRAY"}`}
|
className={`text-R14 ${hasUnread ? "text-BLACK font-bold" : "text-GRAY"}`}
|
||||||
>
|
>
|
||||||
{thread.lastMessage?.content || "بدون پیام"}
|
{thread.lastMessage?.content ||
|
||||||
|
(thread.lastMessage?.product
|
||||||
|
? `🛍 ${thread.lastMessage.product.title || "محصول"}`
|
||||||
|
: "بدون پیام")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -206,15 +206,21 @@ export function useGetThreadMessages(threadId: string) {
|
|||||||
export function useCreateChatThread() {
|
export function useCreateChatThread() {
|
||||||
const token = useAuthToken();
|
const token = useAuthToken();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (participantId: string) => {
|
mutationFn: async (
|
||||||
|
input: string | { participantId: string; productId?: string }
|
||||||
|
) => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Error("Authentication token and user ID are required");
|
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 {
|
try {
|
||||||
const chatApi = createChatApi(token);
|
const chatApi = createChatApi(token);
|
||||||
return await chatApi.apiChatV1ThreadsCreateCreate({
|
return await chatApi.apiChatV1ThreadsCreateCreate({
|
||||||
data: {
|
data: {
|
||||||
participantId: participantId,
|
participantId,
|
||||||
|
...(productId ? { productId } : {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -2336,14 +2336,51 @@ export interface LastMessage {
|
|||||||
*/
|
*/
|
||||||
readonly senderUsername?: string;
|
readonly senderUsername?: string;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof LastMessage
|
* @memberof LastMessage
|
||||||
*/
|
*/
|
||||||
readonly createdAt?: string;
|
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;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
* @export
|
* @export
|
||||||
* @interface Login
|
* @interface Login
|
||||||
*/
|
*/
|
||||||
@ -2479,11 +2516,17 @@ export interface MessageList {
|
|||||||
*/
|
*/
|
||||||
readonly isDeleted?: boolean;
|
readonly isDeleted?: boolean;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
* @memberof MessageList
|
* @memberof MessageList
|
||||||
*/
|
*/
|
||||||
readonly isThreadCreator?: boolean;
|
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
|
* @memberof ThreadCreate
|
||||||
*/
|
*/
|
||||||
participantId: string;
|
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