This commit is contained in:
payacom
2026-07-10 18:17:11 +03:30
parent ce1becc392
commit 2863cdf490
13 changed files with 121 additions and 78 deletions

View File

@@ -1,33 +1,35 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
const CACHE_NAME = "pwa-cache-v2";
// نصب Service Worker
self.addEventListener("install", (event) => {
console.log("Service Worker installing...");
event.waitUntil(
caches.open("pwa-cache-v1").then((cache) => {
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll([
"/",
"/manifest.json",
"/images/icons/256x256.png",
"/images/icons/512x512.png"
"/images/icons/512x512.png",
]);
})
);
self.skipWaiting();
});
// فعال‌سازی Service Worker
self.addEventListener("activate", (event) => {
console.log("Service Worker activated.");
// پاک کردن cacheهای قدیمی
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(
keys
.filter((key) => key !== "pwa-cache-v1")
.filter((key) => key !== CACHE_NAME)
.map((key) => caches.delete(key))
)
)
).then(() => self.clients.claim())
);
});
@@ -35,7 +37,23 @@ self.addEventListener("activate", (event) => {
self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);
// فقط برای فایل‌های استاتیک از cache استفاده کن
// آیکون‌ها: network-first تا تغییرات فوری اعمال شوند
if (url.pathname.startsWith("/images/icons/")) {
event.respondWith(
fetch(event.request)
.then((res) => {
if (res.ok) {
const clone = res.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
}
return res;
})
.catch(() => caches.match(event.request))
);
return;
}
// سایر فایل‌های استاتیک: stale-while-revalidate
if (
url.pathname.startsWith("/images/") ||
url.pathname.endsWith(".png") ||
@@ -46,17 +64,15 @@ self.addEventListener("fetch", (event) => {
url.pathname.endsWith("manifest.json")
) {
event.respondWith(
caches.match(event.request).then((response) => {
return (
response ||
fetch(event.request).then((res) => {
// فایل جدید رو هم ذخیره کن
return caches.open("pwa-cache-v1").then((cache) => {
cache.put(event.request, res.clone());
return res;
});
})
);
caches.match(event.request).then((cached) => {
const networkFetch = fetch(event.request).then((res) => {
if (res.ok) {
const clone = res.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
}
return res;
});
return cached || networkFetch;
})
);
}

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
<url><loc>https://modstagram.com/robots.txt</loc><lastmod>2026-07-08T17:38:00.874Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://modstagram.com/sitemap.xml</loc><lastmod>2026-07-08T17:38:00.875Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://modstagram.com/explore</loc><lastmod>2026-07-08T17:38:00.875Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://modstagram.com/academy/payment/failed</loc><lastmod>2026-07-08T17:38:00.875Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://modstagram.com/about-us</loc><lastmod>2026-07-08T17:38:00.875Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://modstagram.com/academy/payment/success</loc><lastmod>2026-07-08T17:38:00.875Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://modstagram.com/sitemap.xml</loc><lastmod>2026-07-10T11:28:38.990Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://modstagram.com/robots.txt</loc><lastmod>2026-07-10T11:28:38.990Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://modstagram.com/explore</loc><lastmod>2026-07-10T11:28:38.991Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://modstagram.com/academy/payment/success</loc><lastmod>2026-07-10T11:28:38.991Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://modstagram.com/about-us</loc><lastmod>2026-07-10T11:28:38.991Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://modstagram.com/academy/payment/failed</loc><lastmod>2026-07-10T11:28:38.991Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://modstagram.com</loc><changefreq>daily</changefreq><priority>1</priority></url>
<url><loc>https://modstagram.com/projects</loc><changefreq>daily</changefreq><priority>0.9</priority></url>
<url><loc>https://modstagram.com/billboards</loc><changefreq>daily</changefreq><priority>0.9</priority></url>

View File

@@ -16,11 +16,9 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
const description =
post?.caption?.slice(0, 160) ||
"مشاهده پست در مادستاگرام — پلتفرم مدلینگ و تبلیغات";
const imagePath = post?.files?.[0]?.path?.replace(
"/root/modstagram-back/storage",
""
);
const ogImage = imagePath ? `${IMAGE_BASE_URL}${imagePath}` : undefined;
const ogImage = post?.files?.[0]?.path
? buildStorageUrl(post.files[0].path)
: undefined;
return {
title,

View File

@@ -253,34 +253,52 @@ function TicketChat({ params }: ITicketChatProps) {
}
}
const formData = new FormData();
formData.append("content", textContent);
formData.append("receiverId", targetReceiverId);
formData.append("senderId", currentUserId);
if (replySource?._id && !replySource._id.startsWith("temp")) {
formData.append("replyToId", replySource._id);
}
if (fileForUpload) {
formData.append("file", fileForUpload);
if (fileType) formData.append("fileType", fileType);
}
if (selfDestructSeconds) {
formData.append("selfDestructSeconds", String(selfDestructSeconds));
}
if (applyViewOnce) {
formData.append("viewOnce", "true");
}
const isFileUpload = fileForUpload && fileType !== "location";
try {
const endpoint =
fileToUpload && fileType !== "location" ? "/chat/file" : "/chat";
let response: ChatMessage | { data: ChatMessage };
const response = await request<ChatMessage | { data: ChatMessage }>(
"post",
endpoint,
formData,
{ headers: { "Content-Type": "multipart/form-data" } }
);
if (isFileUpload) {
const formData = new FormData();
formData.append("content", textContent);
formData.append("receiverId", targetReceiverId);
formData.append("senderId", currentUserId);
if (replySource?._id && !replySource._id.startsWith("temp")) {
formData.append("replyToId", replySource._id);
}
formData.append("file", fileForUpload);
if (fileType) formData.append("fileType", fileType);
if (selfDestructSeconds) {
formData.append("selfDestructSeconds", String(selfDestructSeconds));
}
if (applyViewOnce) {
formData.append("viewOnce", "true");
}
response = await request<ChatMessage | { data: ChatMessage }>(
"post",
"/chat/file",
formData
);
} else {
const payload: Record<string, unknown> = {
content: textContent,
receiverId: targetReceiverId,
senderId: currentUserId,
};
if (replySource?._id && !replySource._id.startsWith("temp")) {
payload.replyToId = replySource._id;
}
if (selfDestructSeconds) {
payload.selfDestructSeconds = selfDestructSeconds;
}
response = await request<ChatMessage | { data: ChatMessage }>(
"post",
"/chat",
payload
);
}
const serverMsg = normalizeMessage(response as ChatMessage);
const sentMsg: ChatMessage = {
@@ -289,10 +307,10 @@ function TicketChat({ params }: ITicketChatProps) {
replyTo: serverMsg.replyTo ?? replyPayload,
};
if (currentUserId && chatPartnerId) {
if (currentUserId && targetReceiverId) {
linkIds(tempId, sentMsg._id);
queryClient.setQueryData(
chatThreadQueryKey(currentUserId, chatPartnerId),
chatThreadQueryKey(currentUserId, targetReceiverId),
(oldData) => appendMessageToThreadCache(oldData, sentMsg)
);
}

View File

@@ -1,12 +1,15 @@
"use client";
import { useEffect } from "react";
import { ICON_VERSION } from "@/components/main/BaseUrl";
export default function RegisterSW() {
useEffect(() => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/service-worker.js").then(() => {
console.log("Service Worker Registered!");
});
navigator.serviceWorker
.register(`/service-worker.js?v=${ICON_VERSION}`)
.then(() => {
console.log("Service Worker Registered!");
});
}
}, []);

View File

@@ -2,6 +2,7 @@
import { cn } from "@/lib/utils";
import BoldIcon from "@/components/ui/BoldIcon";
import { staticIconUrl } from "@/components/main/BaseUrl";
export type ChatBoldIconName =
| "timer"
@@ -20,9 +21,9 @@ export type ChatBoldIconName =
/** Chat-specific SVG assets (custom uploads) */
const CHAT_ICON_SRC: Partial<Record<ChatBoldIconName, string>> = {
timer: "/images/icons/chat/timer.svg",
search: "/images/icons/chat/search-normal.svg",
back: "/images/icons/chat/back.svg",
timer: staticIconUrl("/images/icons/chat/timer.svg"),
search: staticIconUrl("/images/icons/chat/search-normal.svg"),
back: staticIconUrl("/images/icons/chat/back.svg"),
};
const ICON_NAME: Record<ChatBoldIconName, string> = {

View File

@@ -1,7 +1,7 @@
"use client";
import React, { useEffect, useState, useRef, useMemo, useCallback } from "react";
import { SOCKET_URL } from "../main/BaseUrl";
import { SOCKET_URL, getApiBaseUrl } from "../main/BaseUrl";
import { apiClient } from "@/hooks/useAxios";
import { chatThreadQueryKey } from "@/lib/chat/queryKeys";
import {
@@ -97,7 +97,9 @@ const ChatMessageList = ({
);
const senderId = normalizeThreadId(userDetail?._id ?? getStoredUserId());
const receiverId = normalizeThreadId(chatPartnerId);
const receiverId = normalizeThreadId(
userTwoDetail?._id ?? chatPartnerId
);
const threadReady = Boolean(senderId && receiverId);
const queryKey = chatThreadQueryKey(senderId, receiverId);
@@ -119,7 +121,8 @@ const ChatMessageList = ({
try {
const response = await apiClient.get(
`/chat?senderId=${sid}&receiverId=${rid}&page=${pageParam}&limit=50`
`/chat?senderId=${sid}&receiverId=${rid}&page=${pageParam}&limit=50`,
{ baseURL: getApiBaseUrl() }
);
const msgs = (response.data.messages || []) as ChatMessage[];
return {

View File

@@ -38,6 +38,18 @@ export const BASE_URL = getApiBaseUrl();
/** @deprecated از getStorageBaseUrl() استفاده کنید */
export const IMAGE_BASE_URL = getStorageBaseUrl();
/** نسخه آیکون‌ها — با هر تغییر آیکون این مقدار را افزایش دهید */
export const ICON_VERSION =
process.env.NEXT_PUBLIC_ICON_VERSION ?? "2";
/** URL آیکون استاتیک با cache-busting */
export function staticIconUrl(path: string): string {
if (!path) return "";
const normalized = path.startsWith("/") ? path : `/${path}`;
const separator = normalized.includes("?") ? "&" : "?";
return `${normalized}${separator}v=${ICON_VERSION}`;
}
/** ساخت URL کامل فایل storage — مسیرهای DB را یکسان می‌کند */
export function buildStorageUrl(path: string | null | undefined): string {
if (!path) return "";

View File

@@ -88,10 +88,7 @@ function MainModelCard({
const mediaFiles =
files?.map((file) => ({
...file,
src: `${IMAGE_BASE_URL}${file.path.replace(
"/root/modstagram-back/storage",
""
)}`,
src: buildStorageUrl(file.path),
})) || [];
// تابع برای ایجاد افکت ripple و تغییر وضعیت صدا

View File

@@ -71,10 +71,7 @@ function MainModelCard({ postData }: { postData: Post }) {
const mediaFiles =
files?.map((file) => ({
...file,
src: `${IMAGE_BASE_URL}${file.path.replace(
"/root/modstagram-back/storage",
""
)}`,
src: buildStorageUrl(file.path),
})) || [];
const sliderSettings = {

View File

@@ -92,7 +92,7 @@ function ModelContentPosts({ filters, token, _id }: InfinitePostsProps) {
const mediaFiles =
post.files?.map((file) => ({
...file,
src: `${IMAGE_BASE_URL}${file.path.replace("/root/modstagram-back/storage", "")}`,
src: buildStorageUrl(file.path),
})) || [];
const firstMedia = mediaFiles[0]?.src || "/images/placeholder.png";

View File

@@ -64,10 +64,7 @@ export default function ReelsPostCard({
const mediaFiles =
files?.map((file) => ({
...file,
src: `${IMAGE_BASE_URL}${file.path.replace(
"/root/modstagram-back/storage",
""
)}`,
src: buildStorageUrl(file.path),
})) || [];
const handleVideoClick = (e: React.MouseEvent<HTMLVideoElement>) => {

View File

@@ -1,6 +1,7 @@
"use client";
import { cn } from "@/lib/utils";
import { staticIconUrl } from "@/components/main/BaseUrl";
const BOLD_BASE = "/images/icons/vuesax/bold";
@@ -17,7 +18,7 @@ interface BoldIconProps {
export function boldIconUrl(name: string): string {
const file = name.endsWith(".svg") ? name : `${name}.svg`;
return `${BOLD_BASE}/${file}`;
return staticIconUrl(`${BOLD_BASE}/${file}`);
}
export default function BoldIcon({