feat: implement sellers serializers and frontend details views
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 1m53s

This commit is contained in:
vickytechkey 2026-11-08 17:30:31 +05:30
parent 7b4f51e23d
commit d7aa44979e
3 changed files with 101 additions and 9 deletions

View file

@ -1,6 +1,30 @@
import os
import re
from decimal import Decimal
from django.db.models import Sum, Q
from rest_framework import serializers
CDN_DOMAIN = os.environ.get("AWS_CLOUDFRONT_DOMAIN", "https://dc23efa04hj6d.cloudfront.net").rstrip("/")
if not CDN_DOMAIN.startswith("http"):
CDN_DOMAIN = f"https://{CDN_DOMAIN}"
def get_cdn_url(image_url):
if not image_url:
return image_url
url_str = str(image_url).strip()
if not url_str or url_str.startswith("data:image"):
return url_str
if "dc23efa04hj6d.cloudfront.net" in url_str:
return url_str
if "betasupplierdocumentstorage" in url_str or "amazonaws.com" in url_str or url_str.startswith("supplierdocument/"):
path = url_str.split("?")[0]
path = re.sub(r"^https?://[^/]+/", "", path)
if path.startswith("betasupplierdocumentstorage/"):
path = path[len("betasupplierdocumentstorage/"):]
path = path.lstrip("/")
return f"{CDN_DOMAIN}/{path}"
return url_str
from .models import (
SupplierProfile,
Product,
@ -63,6 +87,19 @@ class ProductSerializer(serializers.ModelSerializer):
model = Product
fields = "__all__"
def to_representation(self, instance):
data = super().to_representation(instance)
img = data.get("image")
if img:
data["image"] = get_cdn_url(img)
add_imgs = data.get("additional_images")
if isinstance(add_imgs, list):
data["additional_images"] = [get_cdn_url(ai) for ai in add_imgs if ai]
video = data.get("video_url")
if video:
data["video_url"] = get_cdn_url(video)
return data
class BulkUploadLogSerializer(serializers.ModelSerializer):
class Meta:

View file

@ -128,6 +128,30 @@
}
}
const CDN_DOMAIN = 'https://dc23efa04hj6d.cloudfront.net';
/** @param {string|null|undefined} url */
function formatMediaUrl(url) {
if (!url) return '';
const str = String(url).trim();
if (!str || str.startsWith('data:image')) return str;
if (str.includes('dc23efa04hj6d.cloudfront.net')) return str;
if (
str.includes('betasupplierdocumentstorage') ||
str.includes('amazonaws.com') ||
str.startsWith('supplierdocument/')
) {
let path = str.split('?')[0];
path = path.replace(/^https?:\/\/[^/]+\//, '');
if (path.startsWith('betasupplierdocumentstorage/')) {
path = path.slice('betasupplierdocumentstorage/'.length);
}
path = path.replace(/^\/+/, '');
return `${CDN_DOMAIN}/${path}`;
}
return str;
}
// Filtered products derived state
let filteredProducts = $derived(() => {
if (productFilter === 'all') return products;
@ -618,7 +642,7 @@
<div style="display:flex;align-items:center;gap:12px;">
{#if p.image}
<a href={`/sellers/${seller.id}/products/${p.id}`} target="_blank" rel="noopener noreferrer" title="Open product in new tab">
<img src={p.image} alt="" style="width:44px;height:44px;border-radius:4px;object-fit:cover;border:1px solid var(--v2-line);cursor:pointer;" />
<img src={formatMediaUrl(p.image)} alt="" style="width:44px;height:44px;border-radius:4px;object-fit:cover;border:1px solid var(--v2-line);cursor:pointer;" />
</a>
{:else}
<a href={`/sellers/${seller.id}/products/${p.id}`} target="_blank" rel="noopener noreferrer" title="Open product in new tab" style="text-decoration:none;">

View file

@ -30,11 +30,36 @@
let seller = $derived(data.seller || {});
let product = $derived(data.product || {});
const CDN_DOMAIN = 'https://dc23efa04hj6d.cloudfront.net';
/** @param {string|null|undefined} url */
function formatMediaUrl(url) {
if (!url) return '';
const str = String(url).trim();
if (!str || str.startsWith('data:image')) return str;
if (str.includes('dc23efa04hj6d.cloudfront.net')) return str;
if (
str.includes('betasupplierdocumentstorage') ||
str.includes('amazonaws.com') ||
str.startsWith('supplierdocument/')
) {
let path = str.split('?')[0];
path = path.replace(/^https?:\/\/[^/]+\//, '');
if (path.startsWith('betasupplierdocumentstorage/')) {
path = path.slice('betasupplierdocumentstorage/'.length);
}
path = path.replace(/^\/+/, '');
return `${CDN_DOMAIN}/${path}`;
}
return str;
}
// Selected active image preview (defaults to product.image)
let selectedImage = $state('');
$effect(() => {
if (product.image && !selectedImage) {
selectedImage = product.image;
const main = formatMediaUrl(product.image);
if (main && !selectedImage) {
selectedImage = main;
}
});
@ -62,10 +87,12 @@
// Parse additional images array safely
let allImages = $derived(() => {
const list = [];
if (product.image) list.push(product.image);
const mainImg = formatMediaUrl(product.image);
if (mainImg) list.push(mainImg);
if (Array.isArray(product.additional_images)) {
for (const img of product.additional_images) {
if (img && !list.includes(img)) list.push(img);
const formatted = formatMediaUrl(img);
if (formatted && !list.includes(formatted)) list.push(formatted);
}
}
return list;
@ -183,7 +210,11 @@
<!-- Main High-Res Preview -->
<div class="main-image-wrap">
{#if selectedImage || product.image}
<img src={selectedImage || product.image} alt={product.title} class="main-preview-img" />
<img
src={formatMediaUrl(selectedImage || product.image)}
alt={product.title}
class="main-preview-img"
/>
{:else}
<div class="main-preview-placeholder">
<Package size={48} style="opacity:0.4;" />
@ -202,7 +233,7 @@
class:active={selectedImage === img || (!selectedImage && idx === 0)}
onclick={() => (selectedImage = img)}
>
<img src={img} alt="" />
<img src={formatMediaUrl(img)} alt="" />
</button>
{/each}
</div>
@ -213,7 +244,7 @@
<div style="margin-top:14px;padding-top:12px;border-top:1px solid var(--v2-line);display:flex;gap:8px;flex-wrap:wrap;">
{#if product.video_url}
<a
href={product.video_url}
href={formatMediaUrl(product.video_url)}
target="_blank"
rel="noopener noreferrer"
class="v2-btn"
@ -225,7 +256,7 @@
{/if}
{#if product.view_360_url}
<a
href={product.view_360_url}
href={formatMediaUrl(product.view_360_url)}
target="_blank"
rel="noopener noreferrer"
class="v2-btn"