diff --git a/src/i18n/locales/en.js b/src/i18n/locales/en.js
index e2530ee..778c260 100644
--- a/src/i18n/locales/en.js
+++ b/src/i18n/locales/en.js
@@ -140,6 +140,7 @@ export default {
photo: "Photo",
brand: "Brand",
code: "Code",
+ channel: "Store",
quantity: "Quantity",
price: "Price",
total: "Total",
diff --git a/src/i18n/locales/ru.js b/src/i18n/locales/ru.js
index 247078b..994f6d5 100644
--- a/src/i18n/locales/ru.js
+++ b/src/i18n/locales/ru.js
@@ -137,6 +137,7 @@ export default {
photo: "Фото",
brand: "Бренд",
code: "Код",
+ channel: "Магазин",
quantity: "Количество",
price: "Цена",
total: "Итого",
diff --git a/src/i18n/locales/tm.js b/src/i18n/locales/tm.js
index 490ae5a..eee4cd4 100644
--- a/src/i18n/locales/tm.js
+++ b/src/i18n/locales/tm.js
@@ -141,6 +141,7 @@ export default {
brand: "Brend",
code: "Kody",
quantity: "Sany",
+ channel: "Magazin",
price: "Bahasy",
total: "Jemi",
orderNumber: "Sargyt belgisi",
diff --git a/src/pages/Category/CategoryPage.module.scss b/src/pages/Category/CategoryPage.module.scss
index 110fd9a..28de483 100644
--- a/src/pages/Category/CategoryPage.module.scss
+++ b/src/pages/Category/CategoryPage.module.scss
@@ -1,3 +1,9 @@
+
+.mobilePhoneGrid {
+ display: flex !important;
+ flex-direction: column;
+ gap: 0;
+}
// Price Filter Styles
.priceFilterContainer {
display: flex;
diff --git a/src/pages/Category/components/Mobilephonecard.jsx b/src/pages/Category/components/Mobilephonecard.jsx
new file mode 100644
index 0000000..458992d
--- /dev/null
+++ b/src/pages/Category/components/Mobilephonecard.jsx
@@ -0,0 +1,324 @@
+import { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import { useTranslation } from "react-i18next";
+import { Modal } from "antd";
+import { IoMdHeartEmpty, IoMdHeart } from "react-icons/io";
+import { FaShoppingCart } from "react-icons/fa";
+import { DecreaseIcon, IncreaseIcon } from "../../../components/Icons";
+import {
+ useAddFavoriteMutation,
+ useRemoveFavoriteMutation,
+ useGetFavoritesQuery,
+} from "../../../app/api/favoritesApi";
+import {
+ useAddToCartMutation,
+ useUpdateCartItemMutation,
+ useRemoveFromCartMutation,
+} from "../../../app/api/cartApi";
+import { useCart } from "../../../app/api/useCart";
+import styles from "./MobilePhoneCard.module.scss";
+
+/**
+ * Parses product.description HTML into spec pairs.
+ * Format inside HTML: "Label1: Value1; Label2: Value2; ..."
+ */
+const parseSpecs = (htmlString) => {
+ if (!htmlString) return [];
+ const div = document.createElement("div");
+ div.innerHTML = htmlString;
+
+ let processedHtml = htmlString
+ .replace(/
/gi, "\n")
+ .replace(/<\/div>/gi, "\n")
+ .replace(/<\/strong>/gi, ": ");
+
+ div.innerHTML = processedHtml;
+ const text = (div.textContent || div.innerText || "").trim();
+
+ return text
+ .split(/[;\n]+/)
+ .map((chunk) => chunk.trim())
+ .filter(Boolean)
+ .map((chunk) => {
+ const separatorIdx = chunk.search(/[:|]/);
+ if (separatorIdx === -1) return null;
+
+ return {
+ label: chunk.slice(0, separatorIdx).trim(),
+ value: chunk.slice(separatorIdx + 1).trim(),
+ };
+ })
+ .filter((item) => item !== null && item.value !== "");
+};
+
+const MobilePhoneCard = ({
+ product,
+ showAddToCart = true,
+ showFavoriteButton = true,
+}) => {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const [stockErrorModalVisible, setStockErrorModalVisible] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+
+ // ── Favorites ──────────────────────────────────────────────────────────────
+ const [addFavorite] = useAddFavoriteMutation();
+ const [removeFavorite] = useRemoveFavoriteMutation();
+ const { data: favoriteProducts = [] } = useGetFavoritesQuery();
+ const [localIsFavorite, setLocalIsFavorite] = useState(false);
+
+ useEffect(() => {
+ if (Array.isArray(favoriteProducts)) {
+ setLocalIsFavorite(
+ favoriteProducts.some((fav) => fav.product?.id === product.id)
+ );
+ }
+ }, [favoriteProducts, product.id]);
+
+ // ── Cart ───────────────────────────────────────────────────────────────────
+ const { getCartItem } = useCart();
+ const [addToCart] = useAddToCartMutation();
+ const [updateCartItem] = useUpdateCartItemMutation();
+ const [removeFromCart] = useRemoveFromCartMutation();
+
+ const cartItem = getCartItem(product.id);
+ const [localQuantity, setLocalQuantity] = useState(0);
+ const [pendingQuantity, setPendingQuantity] = useState(0);
+
+ useEffect(() => {
+ const qty = parseInt(
+ cartItem?.quantity || cartItem?.product_quantity || 0,
+ 10
+ );
+ setLocalQuantity(qty);
+ setPendingQuantity(qty);
+ }, [cartItem]);
+
+ // Debounced sync to server
+ useEffect(() => {
+ const serverQty = parseInt(
+ cartItem?.quantity || cartItem?.product_quantity || 0,
+ 10
+ );
+ if (pendingQuantity === serverQty || pendingQuantity <= 0) return;
+
+ const timer = setTimeout(async () => {
+ try {
+ setIsLoading(true);
+ await updateCartItem({
+ productId: product.id,
+ quantity: pendingQuantity,
+ }).unwrap();
+ } catch {
+ setLocalQuantity(serverQty);
+ setPendingQuantity(serverQty);
+ } finally {
+ setIsLoading(false);
+ }
+ }, 500);
+
+ return () => clearTimeout(timer);
+ }, [pendingQuantity, cartItem, product.id, updateCartItem]);
+
+ // ── Handlers ───────────────────────────────────────────────────────────────
+ const handleCardClick = () => navigate(`/product/${product.id}`);
+
+ const handleAddToCart = async (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (product.stock <= 0) {
+ setStockErrorModalVisible(true);
+ return;
+ }
+ setLocalQuantity((p) => p + 1);
+ setPendingQuantity((p) => p + 1);
+ try {
+ await addToCart({ productId: product.id, quantity: 1 }).unwrap();
+ } catch {
+ setLocalQuantity((p) => p - 1);
+ setPendingQuantity((p) => p - 1);
+ }
+ };
+
+ const handleIncrease = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (isLoading) return;
+ if (localQuantity >= product.stock) {
+ setStockErrorModalVisible(true);
+ return;
+ }
+ setLocalQuantity((p) => p + 1);
+ setPendingQuantity((p) => p + 1);
+ };
+
+ const handleDecrease = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (isLoading) return;
+ if (pendingQuantity <= 1) {
+ setLocalQuantity(0);
+ setPendingQuantity(0);
+ setIsLoading(true);
+ removeFromCart({ productId: product.id })
+ .unwrap()
+ .catch(() => {
+ setLocalQuantity(1);
+ setPendingQuantity(1);
+ })
+ .finally(() => setIsLoading(false));
+ } else {
+ setLocalQuantity((p) => p - 1);
+ setPendingQuantity((p) => p - 1);
+ }
+ };
+
+ const handleToggleFavorite = async (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (isLoading) return;
+ setIsLoading(true);
+ setLocalIsFavorite((prev) => !prev);
+ try {
+ if (localIsFavorite) await removeFavorite(product.id).unwrap();
+ else await addFavorite(product.id).unwrap();
+ } catch {
+ setLocalIsFavorite((prev) => !prev);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ // ── Derived ────────────────────────────────────────────────────────────────
+ const specs = parseSpecs(product.description);
+ const thumbnail =
+ product.media?.[0]?.images_400x400 || product.media?.[0]?.thumbnail;
+ const hasDiscount =
+ product.old_price_amount &&
+ parseFloat(product.old_price_amount) > parseFloat(product.price_amount);
+
+ return (
+ <>
+
+ {/* Image */}
+
+ {product.stock === 0 && (
+
+ {t("common.out_of_stock")}
+
+ )}
+ {thumbnail ? (
+

+ ) : (
+
📱
+ )}
+
+
+ {/* Info */}
+
+
+ {product.name}
+ {product.brand?.name && (
+ {product.brand.name}
+ )}
+
+
+ {/* Dense spec paragraph — mirrors reference image */}
+ {specs.length > 0 && (
+
+ {specs.map((s, i) => (
+
+ {s.label}:
+ {s.value}
+ {i < specs.length - 1 && "; "}
+
+ ))}
+
+ )}
+
+ {/* Footer */}
+
e.stopPropagation()}>
+
+ {product.price_amount} m.
+ {hasDiscount && (
+
+ {product.old_price_amount} m.
+
+ )}
+
+
+
+ {showFavoriteButton && (
+
+ )}
+
+ {showAddToCart &&
+ (localQuantity > 0 ? (
+
+
+ {localQuantity}
+
+
+ ) : (
+
+ ))}
+
+
+
+
+
+ setStockErrorModalVisible(false)}
+ onCancel={() => setStockErrorModalVisible(false)}
+ footer={[
+ ,
+ ]}
+ >
+
+ {t("common.not_enough_stock", {
+ available: product.stock,
+ requested: localQuantity + 1,
+ })}
+
+
+ >
+ );
+};
+
+export const MOBILE_PHONE_CATEGORY_ID = 531;
+export default MobilePhoneCard;
diff --git a/src/pages/Category/components/Mobilephonecard.module.scss b/src/pages/Category/components/Mobilephonecard.module.scss
new file mode 100644
index 0000000..50acd63
--- /dev/null
+++ b/src/pages/Category/components/Mobilephonecard.module.scss
@@ -0,0 +1,270 @@
+// MobilePhoneCard.module.scss
+// Mimics the dense spec-row layout from the reference image (e.g. DNS/Citilink product list)
+
+.card {
+ display: flex;
+ flex-direction: row;
+ gap: 12px;
+ padding: 12px 16px;
+ border-bottom: 1px solid #e8e8e8;
+ background: #fff;
+ cursor: pointer;
+ transition: background 0.15s ease;
+
+ &:first-child {
+ border-top: 1px solid #e8e8e8;
+ }
+
+ &:hover {
+ background: #f5f8ff;
+ }
+}
+
+// ── Image column ─────────────────────────────────────────────
+.imageCol {
+ position: relative;
+ flex-shrink: 0;
+ width: 250px;
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ padding-top: 4px;
+}
+
+.image {
+ width: 250px;
+ height: 260px;
+ object-fit: contain;
+}
+
+.imagePlaceholder {
+ width: 110px;
+ height: 130px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 48px;
+ background: #f0f0f0;
+ border-radius: 4px;
+}
+
+.outOfStockBadge {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background: #999;
+ color: #fff;
+ font-size: 10px;
+ padding: 2px 5px;
+ border-radius: 3px;
+ white-space: nowrap;
+ z-index: 1;
+}
+
+// ── Info column ──────────────────────────────────────────────
+.infoCol {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.titleRow {
+ display: flex;
+ align-items: baseline;
+ gap: 8px;
+ flex-wrap: wrap;
+}
+
+.name {
+ font-size: 14px;
+ font-weight: 600;
+ color: #0645ad;
+ cursor: pointer;
+ line-height: 1.3;
+
+ &:hover {
+ text-decoration: underline;
+ }
+}
+
+.brand {
+ font-size: 11px;
+ color: #888;
+ font-weight: 400;
+ white-space: nowrap;
+}
+
+// Dense spec paragraph — key selling point of this card
+.specLine {
+ margin: 0;
+ font-size: 12px;
+ color: #444;
+ line-height: 1.6;
+ word-break: break-word;
+}
+
+.specLabel {
+ font-weight: 600;
+ color: #222;
+}
+
+.specValue {
+ font-weight: 400;
+ color: #555;
+}
+
+// ── Footer ───────────────────────────────────────────────────
+.footer {
+ margin-top: auto;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 8px;
+ padding-top: 6px;
+ border-top: 1px solid #f0f0f0;
+}
+
+.priceBlock {
+ display: flex;
+ align-items: baseline;
+ gap: 8px;
+}
+
+.price {
+ font-size: 16px;
+ font-weight: 700;
+ color: #c0392b;
+}
+
+.oldPrice {
+ font-size: 12px;
+ color: #aaa;
+ text-decoration: line-through;
+}
+
+// ── Actions ──────────────────────────────────────────────────
+.actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.iconBtn {
+ background: none;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ width: 34px;
+ height: 36px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ color: #aaa;
+ cursor: pointer;
+ transition: color 0.15s, border-color 0.15s;
+
+ &:hover {
+ color: #888;
+ border-color: #888;
+ }
+
+ &.favActive {
+ color: #888;
+ border-color: #888;
+ }
+
+ &:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+}
+
+.cartBtn {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+ background: #d32824;
+ color: #fff;
+ border: none;
+ border-radius: 6px;
+ font-size: 16px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background 0.15s;
+ white-space: nowrap;
+height: 36px;
+ &:hover {
+ background: #c0392b;
+ }
+
+ &:disabled {
+ background: #ccc;
+ cursor: not-allowed;
+ }
+}
+
+// ── Quantity controls ────────────────────────────────────────
+.quantityControls {
+ display: flex;
+ align-items: center;
+ gap: 0;
+ border: 1px solid #d32824;
+ background-color: #d32824;
+ border-radius: 6px;
+ overflow: hidden;
+ min-width: 160px;
+ justify-content: space-between;
+}
+
+.qtyBtn {
+ background: none;
+ border: none;
+ width: 32px;
+ height: 34px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ color: #fff;
+ font-size: 16px;
+ transition: background 0.1s;
+
+ &:hover {
+ background: #e86064;
+ }
+
+ &:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+ }
+}
+
+.qtyValue {
+ min-width: 28px;
+ text-align: center;
+ font-size: 14px;
+ font-weight: 600;
+ color: #fff;
+ padding: 0 4px;
+ line-height: 34px;
+}
+
+// ── Modal ────────────────────────────────────────────────────
+.modalButton {
+ padding: 6px 20px;
+ background: #e74c3c;
+ color: #fff;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 14px;
+
+ &:hover {
+ background: #c0392b;
+ }
+}
\ No newline at end of file
diff --git a/src/pages/Category/index.jsx b/src/pages/Category/index.jsx
index 8f78d2a..1233399 100644
--- a/src/pages/Category/index.jsx
+++ b/src/pages/Category/index.jsx
@@ -16,6 +16,8 @@ import CategoryBreadcrumbs from "./components/CategoryBreadcrumbs";
import useCategoryData from "./hooks/useCategoryData";
import useCategoryProducts from "./hooks/useCategoryProducts";
+import MobilePhoneCard from "./components/Mobilephonecard";
+
const CategoryPage = () => {
const { t } = useTranslation();
const { categoryId, collectionId, brandId } = useParams();
@@ -35,6 +37,13 @@ const CategoryPage = () => {
});
const [isFilterDrawerOpen, setIsFilterDrawerOpen] = useState(false);
+ const [windowWidth, setWindowWidth] = useState(window.innerWidth);
+
+ useEffect(() => {
+ const handleResize = () => setWindowWidth(window.innerWidth);
+ window.addEventListener("resize", handleResize);
+ return () => window.removeEventListener("resize", handleResize);
+ }, []);
const routeKey = useMemo(
() => `${categoryId || "x"}-${collectionId || "x"}-${brandId || "x"}`,
@@ -86,7 +95,10 @@ const CategoryPage = () => {
maxPrice: pageState.maxPrice,
searchQuery,
});
-
+ const isMobilePhoneView =
+ (Number(categoryId) === 531 ||
+ Number(filterState.selectedFilterCategory) === 531) &&
+ windowWidth >= 768;
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
@@ -298,12 +310,20 @@ const CategoryPage = () => {
minPrice={pageState.minPrice}
maxPrice={pageState.maxPrice}
onMinPriceChange={(value) => {
- setPageState((prev) => ({ ...prev, minPrice: value, currentPage: 1 }));
+ setPageState((prev) => ({
+ ...prev,
+ minPrice: value,
+ currentPage: 1,
+ }));
setAllProducts([]);
setHasMore(true);
}}
onMaxPriceChange={(value) => {
- setPageState((prev) => ({ ...prev, maxPrice: value, currentPage: 1 }));
+ setPageState((prev) => ({
+ ...prev,
+ maxPrice: value,
+ currentPage: 1,
+ }));
setAllProducts([]);
setHasMore(true);
}}
@@ -317,10 +337,9 @@ const CategoryPage = () => {
/>
-
{
minPrice={pageState.minPrice}
maxPrice={pageState.maxPrice}
onMinPriceChange={(value) => {
- setPageState((prev) => ({ ...prev, minPrice: value, currentPage: 1 }));
+ setPageState((prev) => ({
+ ...prev,
+ minPrice: value,
+ currentPage: 1,
+ }));
setAllProducts([]);
setHasMore(true);
}}
onMaxPriceChange={(value) => {
- setPageState((prev) => ({ ...prev, maxPrice: value, currentPage: 1 }));
+ setPageState((prev) => ({
+ ...prev,
+ maxPrice: value,
+ currentPage: 1,
+ }));
setAllProducts([]);
setHasMore(true);
}}
@@ -363,16 +390,27 @@ const CategoryPage = () => {
}
- className={styles.productGrid}
+ className={`${styles.productGrid} ${
+ isMobilePhoneView ? styles.mobilePhoneGrid : ""
+ }`}
>
- {filteredProducts.map((product) => (
-
- ))}
+ {filteredProducts.map((product) =>
+ isMobilePhoneView ? (
+
+ ) : (
+
+ )
+ )}
) : (
{t("search.noResults")}
diff --git a/src/pages/ProductDetail/index.jsx b/src/pages/ProductDetail/index.jsx
index 91a36af..7f8647f 100644
--- a/src/pages/ProductDetail/index.jsx
+++ b/src/pages/ProductDetail/index.jsx
@@ -56,7 +56,7 @@ const ProductPage = ({
const { data: favoriteProducts = [] } = useGetFavoritesQuery();
const [isLoading, setIsLoading] = useState(false);
const [localIsFavorite, setLocalIsFavorite] = useState(
- favoriteProducts.some((fav) => fav.product?.id === product?.id),
+ favoriteProducts.some((fav) => fav.product?.id === product?.id)
);
const { getCartItem } = useCart();
@@ -73,7 +73,7 @@ const ProductPage = ({
useEffect(() => {
const qty = parseInt(
cartItem?.quantity || cartItem?.product_quantity || 0,
- 10,
+ 10
);
setLocalQuantity(qty);
setPendingQuantity(qty);
@@ -83,7 +83,7 @@ const ProductPage = ({
useEffect(() => {
if (Array.isArray(favoriteProducts)) {
const isFav = favoriteProducts.some(
- (fav) => fav.product?.id === product?.id,
+ (fav) => fav.product?.id === product?.id
);
setLocalIsFavorite(isFav);
}
@@ -180,7 +180,7 @@ const ProductPage = ({
useEffect(() => {
const serverQty = parseInt(
cartItem?.quantity || cartItem?.product_quantity || 0,
- 10,
+ 10
);
// Sadece miktar değiştiyse ve 0'dan büyükse güncelle (0 ise Remove triggerlanır)
@@ -278,6 +278,15 @@ const ProductPage = ({
{product.brand.name}
)}
+
+ {product.channel?.[0]?.name && (
+
+ {t("order.channel")}
+
+ {product.channel[0].name}
+
+
+ )}