This commit is contained in:
mahmoodsht 2026-05-23 18:55:00 +03:30
parent 388d3da866
commit caef00bb5c
17 changed files with 152 additions and 48 deletions

4
dist/index.html vendored
View File

@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>new hamdast</title>
<title>همدست</title>
<meta name="theme-color" content="#23183E" />
<style>
html,
@ -16,7 +16,7 @@
background: #23183E;
}
</style>
<script type="module" crossorigin src="/assets/index-Ccau0Eyo.js"></script>
<script type="module" crossorigin src="/assets/index-C0TqKym5.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-xouV2fxu.css">
</head>

17
dist/web.config vendored Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="SPA Routes" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/index.html" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

View File

@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>new hamdast</title>
<title>همدست</title>
<meta name="theme-color" content="#23183E" />
<style>
html,

17
public/web.config Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="SPA Routes" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/index.html" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

View File

@ -196,7 +196,7 @@ export function CommentsModal({
<span className="text-xs text-gray-400">{comment.timestamp}</span>
</div>
<p className="text-sm text-white leading-relaxed mb-2">{comment.text}</p>
<p className="text-sm text-white leading-relaxed mb-2 whitespace-pre-line">{comment.text}</p>
{/* Actions */}
<div className="flex items-center gap-4">

View File

@ -1,4 +1,4 @@
import { AnimatePresence, motion } from "motion/react";
import { AnimatePresence, motion } from "motion/react";
import { useState, useEffect, useRef } from "react";
import { useNavigate, useLocation, Navigate } from "react-router-dom";
import { ChevronDown, ChevronRight, ShieldCheck } from "lucide-react";
@ -110,6 +110,20 @@ export function LoginPage() {
return input.replace(/\d/g, (digit) => PERSIAN_NUMBERS[parseInt(digit, 10)]);
};
const parsePhoneNumber = (input: string) => {
const digits = normalizeNumber(input).replace(/\D/g, "").slice(0, 11);
if (/^09\d{9}$/.test(digits)) {
return { error: "", serverMobile: digits };
}
if (/^9\d{9}$/.test(digits)) {
return { error: "", serverMobile: `0${digits}` };
}
return { error: "شماره موبایل معتبر نیست", serverMobile: "" };
};
const clearTimer = () => {
if (timerRef.current) {
clearInterval(timerRef.current);
@ -148,12 +162,16 @@ export function LoginPage() {
const handleSendCode = async (e: React.FormEvent) => {
e.preventDefault();
const { error: phoneError, serverMobile } = parsePhoneNumber(phoneNumber);
if (phoneError) {
setError(phoneError);
return;
}
setIsLoading(true);
setError("");
try {
const normalizedPhone = normalizeNumber(phoneNumber);
const { response, result } = await requestSmsCode(normalizedPhone);
const { response, result } = await requestSmsCode(serverMobile);
if (response.ok && result.state === 0) {
setStep("code");
@ -173,11 +191,15 @@ export function LoginPage() {
const handleVerifyCode = async (e?: React.FormEvent) => {
e?.preventDefault();
const { error: phoneError, serverMobile } = parsePhoneNumber(phoneNumber);
if (phoneError) {
setError(phoneError);
return;
}
setIsLoading(true);
setError("");
try {
const normalizedPhone = normalizeNumber(phoneNumber);
const normalizedCode = normalizeNumber(code);
const response = await fetch(`${API_BASE_URL}/api/verifyloginbysms`, {
@ -186,7 +208,7 @@ export function LoginPage() {
"Content-Type": "application/json",
},
body: JSON.stringify({
mobile: normalizedPhone,
mobile: serverMobile,
code: normalizedCode,
}),
});
@ -223,12 +245,16 @@ export function LoginPage() {
};
const handleResendCode = async () => {
const { error: phoneError, serverMobile } = parsePhoneNumber(phoneNumber);
if (phoneError) {
setError(phoneError);
return;
}
setIsLoading(true);
setError("");
try {
const normalizedPhone = normalizeNumber(phoneNumber);
const { response, result } = await requestSmsCode(normalizedPhone);
const { response, result } = await requestSmsCode(serverMobile);
if (response.ok && result.state === 0) {
setCode("");
@ -394,7 +420,8 @@ export function LoginPage() {
inputMode="numeric"
autoFocus={step === "phone"}
value={toPersianNumber(phoneNumber)}
onChange={(e) => setPhoneNumber(normalizeNumber(e.target.value))}
onChange={(e) => { const nextValue = normalizeNumber(e.target.value).replace(/\D/g, "").slice(0, 11); setPhoneNumber(nextValue); if (error) setError(""); }}
maxLength={11}
className="w-full bg-transparent text-left text-lg text-white outline-none placeholder:text-lg placeholder:text-white/45"
placeholder="۹۱۲ ۱۲۳ ۴۵۶۷"
required
@ -535,3 +562,4 @@ export function LoginPage() {
</div>
);
}

View File

@ -9,6 +9,7 @@ import { useNavigate } from "react-router-dom";
import reactionIconShared from "../../assets/reaction-icon-shared.svg";
import commentIconShared from "../../assets/comment-icon-shared.svg";
import avatarFallbackImage from "../../assets/image 5.png";
import { convertBrToNewlines } from "../../utils/textFormat";
// PostCard با طراحی جدید - قسمت پایین بازطراحی شده + آیکون مشارکت‌کنندگان
interface PostCardProps {
@ -59,6 +60,7 @@ export function PostCard({
teamMemberIds,
preloadedTeamMembers,
}: PostCardProps) {
const normalizedCaption = convertBrToNewlines(caption || "");
const COMMENTS_PAGE_SIZE = 25;
const postChromeStyle = {
border: "1px solid transparent",
@ -419,11 +421,12 @@ export function PostCard({
WebkitLineClamp: expandedCaption ? "unset" : 4,
WebkitBoxOrient: "vertical",
overflow: "hidden",
whiteSpace: "pre-line",
}}
>
{caption}
{normalizedCaption}
</p>
{caption.length > 150 && (
{normalizedCaption.length > 150 && (
<button
onClick={() => setExpandedCaption(!expandedCaption)}
className="text-xs mt-1"

View File

@ -126,7 +126,7 @@ export function PublicChatPage() {
const handleSendMessage = async (message: string) => {
const displayText = message.trim();
const serverText = displayText.replace(/\r?\n+/g, " ").trim();
const serverText = displayText;
if (!serverText || isSending) return;
@ -194,22 +194,8 @@ export function PublicChatPage() {
const result = await loadChatList();
if (result.success) {
const sortedItems = [...result.data].sort((a, b) => {
const timeA = Date.parse(a.datetime1 || "");
const timeB = Date.parse(b.datetime1 || "");
if (!Number.isNaN(timeA) && !Number.isNaN(timeB)) {
return timeB - timeA;
}
return 0;
});
const allDatesInvalid = sortedItems.every((item) =>
Number.isNaN(Date.parse(item.datetime1 || "")),
);
setHistoryItems(allDatesInvalid ? [...result.data].reverse() : sortedItems);
// Server returns oldest -> newest. UI should show newest first.
setHistoryItems([...result.data].reverse());
} else {
console.error("Failed to load chat list:", result.message);
alert(result.message || "خطا در بارگذاری تاریخچه");
@ -244,7 +230,13 @@ export function PublicChatPage() {
}, [handleHistoryClick, isLoading, isSending]);
return (
<div className="relative h-full min-h-0 overflow-hidden">
<div
className="relative h-full min-h-0 overflow-hidden"
style={{
overscrollBehaviorY: "none",
touchAction: "pan-y",
}}
>
<div className="grid h-full min-h-0 grid-rows-[minmax(0,1fr)_auto]">
<main className="relative min-h-0 overflow-hidden">
{isLoading ? (

View File

@ -99,6 +99,9 @@ export function ChatHistoryModal({
"calc(100dvh - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px) - 180px)",
scrollbarWidth: "none",
msOverflowStyle: "none",
overscrollBehaviorY: "contain",
WebkitOverflowScrolling: "touch",
touchAction: "pan-y",
}}
>
{isLoading ? (

View File

@ -54,8 +54,13 @@ export function ChatMessages({
return (
<div
ref={containerRef}
className="flex-1 overflow-y-auto px-4 pb-20"
className="h-full min-h-0 overflow-y-auto px-4 pb-20"
dir="rtl"
style={{
overscrollBehaviorY: "contain",
WebkitOverflowScrolling: "touch",
touchAction: "pan-y",
}}
>
<div className="space-y-3">
{messages.map((message) => {

View File

@ -1,3 +1,4 @@
// API Configuration
export const API_BASE_URL = "http://141.11.1.189";
// export const API_BASE_URL = "https://localhost:44362";
//export const API_BASE_URL = "https://hamdast-back2.sepehrdata.com";

View File

@ -50,7 +50,7 @@ export const useChatFlow = ({ workflowId, onMissionEnd }: UseChatFlowOptions): U
});
const displayMessage = messageText.trim();
const normalizedMessage = displayMessage.replace(/\r?\n+/g, " ").trim();
const normalizedMessage = displayMessage;
if (!normalizedMessage || !workflowId) {
console.log("sendMessage aborted:", {

View File

@ -1,4 +1,5 @@
import { API_BASE_URL } from "../config/api";
import { convertBrToNewlines, convertNewlinesToBr } from "../utils/textFormat";
const AVATAR_CACHE_BUST_KEY = "avatarCacheBust";
@ -82,7 +83,10 @@ const formatErrorMessage = (result: ApiResponse<any>): string => {
const parseFeedData = (data: string): FeedItem[] => {
try {
const parsed = JSON.parse(data);
const feedArray = JSON.parse(parsed.feed || "[]");
const feedArray = JSON.parse(parsed.feed || "[]").map((item: FeedItem) => ({
...item,
description: convertBrToNewlines(item.description || ""),
}));
return feedArray;
} catch (error) {
console.error("Error parsing feed data:", error);
@ -280,7 +284,7 @@ export const saveComment = async (
save_comment_function: {
mission_type: missionType,
mission_done_workflowID: missionDoneWorkflowID,
text: text,
text: convertNewlinesToBr(text || ""),
replay_workflowID: replayWorkflowID,
},
}),
@ -373,7 +377,10 @@ export const loadComments = async (
if (response.ok && result.state === 0) {
const data = JSON.parse(result.data);
const comments = JSON.parse(data.comments);
const comments = JSON.parse(data.comments).map((comment: CommentData) => ({
...comment,
comment_text: convertBrToNewlines(comment.comment_text || ""),
}));
return { comments };
} else {
console.error("Error loading comments:", result.message);
@ -519,7 +526,11 @@ export const startMission = async (
if (response.ok && result.state === 0) {
const data = JSON.parse(result.data);
const doingMissionArray = JSON.parse(data.doing_mission);
const chatsArray = JSON.parse(data.chats);
const chatsArray = JSON.parse(data.chats).map((chat: ChatMessage) => ({
...chat,
question: convertBrToNewlines(chat.question || ""),
answer: convertBrToNewlines(chat.answer || ""),
}));
return {
doing_mission: doingMissionArray.length > 0 ? doingMissionArray[0] : null,
@ -561,7 +572,7 @@ export const sendChatMessage = async (
try {
const payload = {
chat_service_function: {
user_message: userMessage,
user_message: convertNewlinesToBr(userMessage || ""),
mission_done_workflowID: missionDoneWorkflowID,
},
};
@ -587,7 +598,7 @@ export const sendChatMessage = async (
const chatResponse = JSON.parse(data[keys[0]]);
return {
success: true,
message: chatResponse.message,
message: convertBrToNewlines(chatResponse.message || ""),
actions: chatResponse.actions,
is_mission_end: chatResponse.is_mission_end,
};
@ -760,7 +771,7 @@ export const submitMission = async (
title: data.title || "",
mission_type: data.mission_type || "",
mission_done_workflowID: data.mission_done_workflowID || "",
description: data.description || "",
description: convertNewlinesToBr(data.description || ""),
film: data.film || "",
image: data.image || "",
audio: data.audio || "",

View File

@ -1,6 +1,7 @@
// Profile Service - برای مدیریت پروفایل کاربر
import { API_BASE_URL } from "../config/api";
import { getAccessToken } from "../utils/auth";
import { convertBrToNewlines } from "../utils/textFormat";
export interface UserProfile {
username: string;
@ -242,8 +243,18 @@ export const getUserProfileData = async (): Promise<UserProfileData | null> => {
return {
challenges: parsedData.challenges ? JSON.parse(parsedData.challenges) : [],
coin_transaction: parsedData.coin_transaction ? JSON.parse(parsedData.coin_transaction) : [],
posts: parsedData.posts ? JSON.parse(parsedData.posts) : [],
coin_transaction: parsedData.coin_transaction
? JSON.parse(parsedData.coin_transaction).map((item: ProfileCoinTransaction) => ({
...item,
description: convertBrToNewlines(item.description || ""),
}))
: [],
posts: parsedData.posts
? JSON.parse(parsedData.posts).map((post: ProfilePost) => ({
...post,
description: convertBrToNewlines(post.description || ""),
}))
: [],
};
}

View File

@ -1,4 +1,5 @@
import { API_BASE_URL } from "../config/api";
import { convertBrToNewlines, convertNewlinesToBr } from "../utils/textFormat";
const getAuthToken = (): string | null => {
const accessToken = localStorage.getItem("accessToken");
@ -129,10 +130,15 @@ export const loadChat = async (
// Parse the nested JSON string
const parsedData = JSON.parse(result.data);
const chats = JSON.parse(parsedData.chats);
const normalizedChats = chats.map((chat: PublicChatMessage) => ({
...chat,
question: convertBrToNewlines(chat.question || ""),
answer: convertBrToNewlines(chat.answer || ""),
}));
return {
success: true,
data: chats,
data: normalizedChats,
};
} else {
return {
@ -177,7 +183,7 @@ export const sendPublicChatMessage = async (
body: JSON.stringify({
public_caht_function: {
chatlist_workflowID: chatlistWorkflowID,
question: question,
question: convertNewlinesToBr(question || ""),
},
}),
});
@ -191,7 +197,7 @@ export const sendPublicChatMessage = async (
return {
success: true,
answer: messageData.answer,
answer: convertBrToNewlines(messageData.answer || ""),
newChatlistWorkflowID: messageData.chatlist_workflowID2,
};
} else {

View File

@ -1,4 +1,5 @@
import { FeedItem, getAvatarUrl, getFeedImageUrl, getVideoUrl, getAudioUrl, isOwnFeed, type TeamMember } from "../services/feedService";
import { convertBrToNewlines } from "./textFormat";
export interface PostCardModel {
id: string;
@ -63,7 +64,7 @@ export const mapFeedItemToPostCardModel = (
authorAvatar: getAvatarUrl(item.person_stage_id),
image: coverImage,
title: item.title,
caption: item.description,
caption: convertBrToNewlines(item.description || ""),
likes: item.like_count,
dislikes: item.dislike_count,
comments: item.comment_count,

9
src/utils/textFormat.ts Normal file
View File

@ -0,0 +1,9 @@
export const convertNewlinesToBr = (value: string): string => {
if (!value) return "";
return value.replace(/\r\n|\r|\n/g, "<br>");
};
export const convertBrToNewlines = (value: string): string => {
if (!value) return "";
return value.replace(/<br\s*\/?>/gi, "\n");
};