feat: implement sellers serializers and frontend details views
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 1m53s
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 1m53s
This commit is contained in:
parent
7b4f51e23d
commit
d7aa44979e
3 changed files with 101 additions and 9 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;">
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue