add top selling and card component
21
api/admin.py
|
|
@ -1,3 +1,22 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
# Register your models here.
|
from .models import Category, Product
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Category)
|
||||||
|
class CategoryAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'slug')
|
||||||
|
prepopulated_fields = {'slug': ('name',)}
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Product)
|
||||||
|
class ProductAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
'name',
|
||||||
|
'category',
|
||||||
|
'price',
|
||||||
|
'old_price',
|
||||||
|
'rating',
|
||||||
|
)
|
||||||
|
list_filter = ('category',)
|
||||||
|
search_fields = ('name',)
|
||||||
43
api/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Generated by Django 6.1.1 on 2026-09-18 11:45
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Category',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=100)),
|
||||||
|
('slug', models.SlugField(unique=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Category',
|
||||||
|
'verbose_name_plural': 'Categories',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Product',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=200)),
|
||||||
|
('image', models.ImageField(upload_to='products/')),
|
||||||
|
('rating', models.DecimalField(decimal_places=1, max_digits=2)),
|
||||||
|
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||||
|
('old_price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
|
||||||
|
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='api.category')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Product',
|
||||||
|
'verbose_name_plural': 'Products',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,3 +1,47 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
|
class Category(models.Model):
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
slug = models.SlugField(unique=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Category'
|
||||||
|
verbose_name_plural = 'Categories'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class Product(models.Model):
|
||||||
|
category = models.ForeignKey(
|
||||||
|
Category,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='products',
|
||||||
|
)
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
image = models.ImageField(upload_to='products/')
|
||||||
|
rating = models.DecimalField(max_digits=2, decimal_places=1)
|
||||||
|
price = models.DecimalField(max_digits=10, decimal_places=2)
|
||||||
|
old_price = models.DecimalField(
|
||||||
|
max_digits=10,
|
||||||
|
decimal_places=2,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def discount_percent(self):
|
||||||
|
if self.old_price and self.old_price > self.price:
|
||||||
|
return round(
|
||||||
|
(self.old_price - self.price) / self.old_price * 100
|
||||||
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Product'
|
||||||
|
verbose_name_plural = 'Products'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
20
api/serializers.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from .models import Product
|
||||||
|
|
||||||
|
|
||||||
|
class ProductSerializer(serializers.ModelSerializer):
|
||||||
|
discount_percent = serializers.ReadOnlyField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Product
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'image',
|
||||||
|
'rating',
|
||||||
|
'price',
|
||||||
|
'old_price',
|
||||||
|
'discount_percent',
|
||||||
|
'category',
|
||||||
|
)
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import hello
|
from .views import hello, ProductListView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("hello/", hello),
|
path("hello/", hello),
|
||||||
|
path("products/", ProductListView.as_view()),
|
||||||
]
|
]
|
||||||
11
api/views.py
|
|
@ -1,9 +1,18 @@
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.decorators import api_view
|
from rest_framework.decorators import api_view
|
||||||
|
from rest_framework.generics import ListAPIView
|
||||||
|
|
||||||
|
from .models import Product
|
||||||
|
from .serializers import ProductSerializer
|
||||||
|
|
||||||
|
|
||||||
@api_view(["GET"])
|
@api_view(["GET"])
|
||||||
def hello(request):
|
def hello(request):
|
||||||
return Response({
|
return Response({
|
||||||
"message": "Hello from Freebie API!"
|
"message": "Hello from Freebie API!"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class ProductListView(ListAPIView):
|
||||||
|
queryset = Product.objects.all()
|
||||||
|
serializer_class = ProductSerializer
|
||||||
|
|
@ -40,6 +40,7 @@ INSTALLED_APPS = [
|
||||||
# =============================
|
# =============================
|
||||||
"corsheaders",
|
"corsheaders",
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
|
"api",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|
@ -106,7 +107,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/6.1/topics/i18n/
|
# https://docs.djangoproject.com/en/6.1/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = 'ru-RU'
|
||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
|
@ -132,4 +133,7 @@ MAILERS = {
|
||||||
|
|
||||||
CORS_ALLOWED_ORIGINS = [
|
CORS_ALLOWED_ORIGINS = [
|
||||||
"http://localhost:5173",
|
"http://localhost:5173",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
MEDIA_URL = "/media/"
|
||||||
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
|
@ -1,7 +1,15 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("api/", include("api.urls")),
|
path("api/", include("api.urls")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
urlpatterns += static(
|
||||||
|
settings.MEDIA_URL,
|
||||||
|
document_root=settings.MEDIA_ROOT,
|
||||||
|
)
|
||||||
67
frontend/src/components/NewArrivals/NewArrivals.module.css
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
.section {
|
||||||
|
border-top: 3px solid #000;
|
||||||
|
padding: 48px 0 12px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section :global(.container) {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0 0 30px;
|
||||||
|
color: #000;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.products {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewAll {
|
||||||
|
display: block;
|
||||||
|
width: 138px;
|
||||||
|
height: 36px;
|
||||||
|
margin: 27px auto 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid #e5e5e5;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #fff;
|
||||||
|
color: #000;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewAll:hover {
|
||||||
|
background: #f7f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.products {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
row-gap: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 420px) {
|
||||||
|
.section {
|
||||||
|
padding-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.products {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
58
frontend/src/components/NewArrivals/NewArrivals.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import styles from './NewArrivals.module.css'
|
||||||
|
import ProductsCard from '../ProductCard/ProductCard'
|
||||||
|
|
||||||
|
type Product = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
image: string
|
||||||
|
rating: number
|
||||||
|
price: string
|
||||||
|
old_price: string | null
|
||||||
|
discount_percent: number | null
|
||||||
|
category: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function NewArrivals() {
|
||||||
|
const [products, setProducts] = useState<Product[]>([])
|
||||||
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('http://127.0.0.1:8000/api/products/')
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
setProducts(data)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoading(false)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={styles.section}>
|
||||||
|
<div className="container">
|
||||||
|
<h2 className={styles.title}>NEW ARRIVALS</h2>
|
||||||
|
|
||||||
|
<div className={styles.products}>
|
||||||
|
{products.slice(0, 4).map((product) => (
|
||||||
|
<ProductsCard
|
||||||
|
key={product.id}
|
||||||
|
product={product}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" className={styles.viewAll}>
|
||||||
|
View All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewArrivals
|
||||||
86
frontend/src/components/ProductCard/ProductCard.module.css
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
.card {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageWrapper {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #f1efef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
margin: 11px 0 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #000;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.25;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rating {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
height: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stars {
|
||||||
|
color: #ffc633;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratingValue {
|
||||||
|
color: #555;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priceRow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 9px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
color: #000;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oldPrice {
|
||||||
|
color: #999;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discount {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 39px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 7px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #ffebeb;
|
||||||
|
color: #ff3333;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
59
frontend/src/components/ProductCard/ProductCard.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import styles from './ProductCard.module.css'
|
||||||
|
|
||||||
|
type Product = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
image: string
|
||||||
|
rating: number
|
||||||
|
price: string
|
||||||
|
old_price: string | null
|
||||||
|
discount_percent: number | null
|
||||||
|
category: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductsCardProps = {
|
||||||
|
product: Product
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProductsCard({ product }: ProductsCardProps) {
|
||||||
|
return (
|
||||||
|
<article className={styles.card}>
|
||||||
|
<div className={styles.imageWrapper}>
|
||||||
|
<img
|
||||||
|
src={product.image}
|
||||||
|
alt={product.name}
|
||||||
|
className={styles.image}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className={styles.name}>{product.name}</h3>
|
||||||
|
|
||||||
|
<div className={styles.rating}>
|
||||||
|
<span className={styles.stars}>★★★★★</span>
|
||||||
|
<span className={styles.ratingValue}>
|
||||||
|
{product.rating}/5
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.priceRow}>
|
||||||
|
<span className={styles.price}>
|
||||||
|
${Number(product.price).toFixed(0)}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{product.old_price && (
|
||||||
|
<span className={styles.oldPrice}>
|
||||||
|
${Number(product.old_price).toFixed(0)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{product.discount_percent && (
|
||||||
|
<span className={styles.discount}>
|
||||||
|
-{product.discount_percent}%
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProductsCard
|
||||||
67
frontend/src/components/TopSelling/TopSelling.module.css
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
.section {
|
||||||
|
border-top: 3px solid #00000010;
|
||||||
|
padding: 48px 0 12px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section :global(.container) {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 30px 0 30px;
|
||||||
|
color: #000;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.products {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewAll {
|
||||||
|
display: block;
|
||||||
|
width: 138px;
|
||||||
|
height: 36px;
|
||||||
|
margin: 27px auto 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid #e5e5e5;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #fff;
|
||||||
|
color: #000;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewAll:hover {
|
||||||
|
background: #f7f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.products {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
row-gap: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 420px) {
|
||||||
|
.section {
|
||||||
|
padding-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.products {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
58
frontend/src/components/TopSelling/TopSelling.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import styles from './TopSelling.module.css'
|
||||||
|
import ProductsCard from '../ProductCard/ProductCard'
|
||||||
|
|
||||||
|
type Product = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
image: string
|
||||||
|
rating: number
|
||||||
|
price: string
|
||||||
|
old_price: string | null
|
||||||
|
discount_percent: number | null
|
||||||
|
category: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function TopSelling() {
|
||||||
|
const [products, setProducts] = useState<Product[]>([])
|
||||||
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('http://127.0.0.1:8000/api/products/')
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
setProducts(data)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoading(false)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={`${styles.section} container`}>
|
||||||
|
<div className="container">
|
||||||
|
<h2 className={styles.title}>TOP SELLING</h2>
|
||||||
|
|
||||||
|
<div className={styles.products}>
|
||||||
|
{products.slice(4, 8).map((product) => (
|
||||||
|
<ProductsCard
|
||||||
|
key={product.id}
|
||||||
|
product={product}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" className={styles.viewAll}>
|
||||||
|
View All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TopSelling
|
||||||
|
|
@ -2,6 +2,8 @@ import AnnouncementBar from '../../components/AnnouncementBar/AnnouncementBar'
|
||||||
import Header from '../../components/Header/Header'
|
import Header from '../../components/Header/Header'
|
||||||
import Hero from '../../components/Hero/Hero'
|
import Hero from '../../components/Hero/Hero'
|
||||||
import Brends from '../../components/Brends/Brends'
|
import Brends from '../../components/Brends/Brends'
|
||||||
|
import Arrivals from '../../components/NewArrivals/NewArrivals'
|
||||||
|
import TopSelling from '../../components/TopSelling/TopSelling'
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
return (
|
return (
|
||||||
|
|
@ -13,6 +15,10 @@ function Home() {
|
||||||
<Hero />
|
<Hero />
|
||||||
|
|
||||||
<Brends />
|
<Brends />
|
||||||
|
|
||||||
|
<Arrivals />
|
||||||
|
|
||||||
|
<TopSelling />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
media/products/Courage.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
media/products/Faded.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
media/products/Loose.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
media/products/Striped.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
media/products/checkered.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
media/products/skinny.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
media/products/sleeve.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
media/products/t-shirt.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
|
|
@ -1,3 +1,4 @@
|
||||||
Django==6.1.1
|
Django==6.1.1
|
||||||
django-cors-headers==4.9.0
|
django-cors-headers==4.9.0
|
||||||
djangorestframework==3.18.1
|
djangorestframework==3.18.1
|
||||||
|
pillow==12.3.0
|
||||||