Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ed4b35c3c | ||
|
|
e1b2abf2d5 | ||
|
|
5082c8c2c8 | ||
|
|
c12c6efa89 | ||
|
|
b0d2c02bf5 | ||
|
|
4ea794681e | ||
|
|
0562fa6c32 | ||
|
|
658ba85e2b | ||
|
|
1ad6b959ed | ||
|
|
d6a0b8edb5 | ||
|
|
e87d7c53d1 | ||
|
|
968b83badc | ||
|
|
1419674f5b | ||
|
|
1fae2204d0 | ||
|
|
3a45bf3328 | ||
|
|
81d9c6f6ac | ||
|
|
1b8ef692d3 | ||
|
|
d276e2f765 | ||
|
|
d8fd7aae82 | ||
|
|
a6552f1692 | ||
|
|
7563692b8b |
Binary file not shown.
BIN
backend/config/__pycache__/settings.cpython-311.pyc
Normal file
BIN
backend/config/__pycache__/settings.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/config/__pycache__/urls.cpython-311.pyc
Normal file
BIN
backend/config/__pycache__/urls.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
@ -5,9 +5,6 @@ Generated by 'django-admin startproject' using Django 5.2.7.
|
|||||||
|
|
||||||
For more information on this file, see
|
For more information on this file, see
|
||||||
https://docs.djangoproject.com/en/5.2/topics/settings/
|
https://docs.djangoproject.com/en/5.2/topics/settings/
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/5.2/ref/settings/
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -55,12 +52,15 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'rest_framework',
|
||||||
|
'corsheaders',
|
||||||
'core',
|
'core',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'corsheaders.middleware.CorsMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
@ -76,7 +76,7 @@ ROOT_URLCONF = 'config.urls'
|
|||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [],
|
'DIRS': [BASE_DIR.parent / 'frontend' / 'dist'],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
@ -151,8 +151,8 @@ STATIC_ROOT = BASE_DIR / 'staticfiles'
|
|||||||
|
|
||||||
STATICFILES_DIRS = [
|
STATICFILES_DIRS = [
|
||||||
BASE_DIR / 'static',
|
BASE_DIR / 'static',
|
||||||
BASE_DIR / 'assets',
|
BASE_DIR.parent / 'frontend' / 'dist',
|
||||||
BASE_DIR / 'node_modules',
|
BASE_DIR.parent / 'frontend' / 'dist' / 'assets',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Email
|
# Email
|
||||||
@ -180,3 +180,20 @@ if EMAIL_USE_SSL:
|
|||||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
CORS_ALLOWED_ORIGINS = [
|
||||||
|
"http://localhost:3000",
|
||||||
|
"http://127.0.0.1:3000",
|
||||||
|
]
|
||||||
|
CORS_ALLOW_CREDENTIALS = True
|
||||||
|
CORS_ALLOW_ALL_ORIGINS = True
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_PERMISSION_CLASSES': [
|
||||||
|
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
|
||||||
|
],
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||||
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
|
'rest_framework.authentication.BasicAuthentication',
|
||||||
|
],
|
||||||
|
}
|
||||||
@ -15,15 +15,15 @@ Including another URLconf
|
|||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import include, path
|
from django.urls import include, path, re_path
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
from django.views.static import serve
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
|
# Serve assets and static files explicitly before the catch-all
|
||||||
|
re_path(r'^assets/(?P<path>.*)$', serve, {'document_root': settings.BASE_DIR.parent / "frontend" / "dist" / "assets"}),
|
||||||
|
re_path(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATIC_ROOT}),
|
||||||
path("", include("core.urls")),
|
path("", include("core.urls")),
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
|
||||||
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
|
|
||||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/core/__pycache__/models.cpython-311.pyc
Normal file
BIN
backend/core/__pycache__/models.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/core/__pycache__/serializers.cpython-311.pyc
Normal file
BIN
backend/core/__pycache__/serializers.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/core/__pycache__/urls.cpython-311.pyc
Normal file
BIN
backend/core/__pycache__/urls.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/core/__pycache__/views.cpython-311.pyc
Normal file
BIN
backend/core/__pycache__/views.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/core/management/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
backend/core/management/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
18
backend/core/management/commands/load_demo_data.py
Normal file
18
backend/core/management/commands/load_demo_data.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from core.models import Product
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Populates the database with demo products'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
Product.objects.all().delete()
|
||||||
|
demo_data = [
|
||||||
|
{"name": "Ethiopian Specialty Coffee", "price": 12.99, "description": "Authentic Yirgacheffe coffee beans, medium roast."},
|
||||||
|
{"name": "Traditional Woven Scarf", "price": 25.50, "description": "Hand-woven cotton scarf with cultural patterns."},
|
||||||
|
{"name": "Handmade Clay Vase", "price": 18.00, "description": "Beautifully crafted artisan clay vase for home decor."},
|
||||||
|
{"name": "Organic Teff Flour", "price": 8.50, "description": "High-quality, gluten-free organic teff flour, 1kg."},
|
||||||
|
{"name": "Leather Journal", "price": 15.00, "description": "Hand-stitched genuine leather notebook, perfect for sketching."},
|
||||||
|
]
|
||||||
|
for item in demo_data:
|
||||||
|
Product.objects.create(**item)
|
||||||
|
self.stdout.write(self.style.SUCCESS('Successfully added 5 demo products!'))
|
||||||
25
backend/core/migrations/0001_initial.py
Normal file
25
backend/core/migrations/0001_initial.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-03-12 23:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
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)),
|
||||||
|
('description', models.TextField()),
|
||||||
|
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||||
|
('image', models.ImageField(blank=True, null=True, upload_to='products/')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
32
backend/core/migrations/0002_seller_product_seller.py
Normal file
32
backend/core/migrations/0002_seller_product_seller.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-03-13 00:39
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Seller',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('store_name', models.CharField(max_length=200)),
|
||||||
|
('description', models.TextField(blank=True)),
|
||||||
|
('is_verified', models.BooleanField(default=False)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='product',
|
||||||
|
name='seller',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='products', to='core.seller'),
|
||||||
|
),
|
||||||
|
]
|
||||||
33
backend/core/migrations/0003_category_product_category.py
Normal file
33
backend/core/migrations/0003_category_product_category.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-03-13 01:38
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0002_seller_product_seller'),
|
||||||
|
]
|
||||||
|
|
||||||
|
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, unique=True)),
|
||||||
|
('slug', models.SlugField(unique=True)),
|
||||||
|
('description', models.TextField(blank=True)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='core.category')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'categories',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='product',
|
||||||
|
name='category',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='products', to='core.category'),
|
||||||
|
),
|
||||||
|
]
|
||||||
BIN
backend/core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
BIN
backend/core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
37
backend/core/models.py
Normal file
37
backend/core/models.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
class Category(models.Model):
|
||||||
|
name = models.CharField(max_length=100, unique=True)
|
||||||
|
slug = models.SlugField(unique=True)
|
||||||
|
description = models.TextField(blank=True)
|
||||||
|
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = 'categories'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Seller(models.Model):
|
||||||
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||||
|
store_name = models.CharField(max_length=200)
|
||||||
|
description = models.TextField(blank=True)
|
||||||
|
is_verified = models.BooleanField(default=False)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.store_name
|
||||||
|
|
||||||
|
class Product(models.Model):
|
||||||
|
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, related_name='products')
|
||||||
|
seller = models.ForeignKey(Seller, on_delete=models.CASCADE, related_name='products', null=True, blank=True)
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
description = models.TextField()
|
||||||
|
price = models.DecimalField(max_digits=10, decimal_places=2)
|
||||||
|
image = models.ImageField(upload_to='products/', null=True, blank=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
17
backend/core/serializers.py
Normal file
17
backend/core/serializers.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from .models import Seller, Product, Category
|
||||||
|
|
||||||
|
class CategorySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Category
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class SellerSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Seller
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class ProductSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Product
|
||||||
|
fields = '__all__'
|
||||||
41
backend/core/templates/base.html
Normal file
41
backend/core/templates/base.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{% block title %}Ethio-Gebeya{% endblock %}</title>
|
||||||
|
{% load static %}
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
|
||||||
|
{% block head %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body class="d-flex flex-column min-vh-100">
|
||||||
|
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm py-3">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand fw-bold" href="/">ETHIO-GEBEYA</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav ms-auto">
|
||||||
|
<li class="nav-item"><a class="nav-link" href="/">Shop</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link" href="#">Account</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="flex-grow-1">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="bg-dark text-white py-4 mt-auto">
|
||||||
|
<div class="container text-center">
|
||||||
|
<p class="mb-0">© 2026 Ethio-Gebeya. Professional Market Solutions.</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
33
backend/core/templates/core/index.html
Normal file
33
backend/core/templates/core/index.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Ethio-Gebeya | Modern Marketplace{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<header class="hero text-white py-5">
|
||||||
|
<div class="container text-center">
|
||||||
|
<h1 class="display-3 mb-3">Welcome to Ethio-Gebeya</h1>
|
||||||
|
<p class="lead mb-4">Discover the best quality goods in one place.</p>
|
||||||
|
<a href="#products" class="btn btn-lg px-4" style="background-color: var(--accent); color: white;">Start Shopping</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section id="products" class="container py-5">
|
||||||
|
<h2 class="text-center mb-5 text-dark">Featured Products</h2>
|
||||||
|
<div class="row row-cols-1 row-cols-md-3 g-4">
|
||||||
|
{% for product in products %}
|
||||||
|
<div class="col">
|
||||||
|
<article class="product-card h-100 shadow-sm border-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">{{ product.name }}</h5>
|
||||||
|
<p class="card-text text-muted">{{ product.description|truncatewords:15 }}</p>
|
||||||
|
<p class="h5 fw-bold text-primary">ETB {{ product.price }}</p>
|
||||||
|
<a href="#" class="btn btn-outline-primary mt-3">View Details</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<p class="text-center w-100">No products available yet.</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
13
backend/core/urls.py
Normal file
13
backend/core/urls.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from django.urls import path, include, re_path
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
from .views import SellerViewSet, ProductViewSet, CategoryViewSet, serve_frontend
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register(r'api/categories', CategoryViewSet)
|
||||||
|
router.register(r'api/sellers', SellerViewSet)
|
||||||
|
router.register(r'api/products', ProductViewSet)
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('api/', include(router.urls)),
|
||||||
|
re_path(r'^.*$', serve_frontend, name='serve_frontend'),
|
||||||
|
]
|
||||||
19
backend/core/views.py
Normal file
19
backend/core/views.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
from rest_framework import viewsets
|
||||||
|
from .models import Seller, Product, Category
|
||||||
|
from .serializers import SellerSerializer, ProductSerializer, CategorySerializer
|
||||||
|
|
||||||
|
def serve_frontend(request, path=''):
|
||||||
|
return render(request, 'index.html')
|
||||||
|
|
||||||
|
class CategoryViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = Category.objects.all()
|
||||||
|
serializer_class = CategorySerializer
|
||||||
|
|
||||||
|
class SellerViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = Seller.objects.all()
|
||||||
|
serializer_class = SellerSerializer
|
||||||
|
|
||||||
|
class ProductViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = Product.objects.all()
|
||||||
|
serializer_class = ProductSerializer
|
||||||
6
backend/requirements.txt
Normal file
6
backend/requirements.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Django==5.2.7
|
||||||
|
mysqlclient==2.2.7
|
||||||
|
python-dotenv==1.1.1
|
||||||
|
djangorestframework
|
||||||
|
djangorestframework-simplejwt
|
||||||
|
django-cors-headers
|
||||||
59
backend/static/css/custom.css
Normal file
59
backend/static/css/custom.css
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/* Professional Design - Ethio-Gebeya */
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Manrope:wght@600;700;800&display=swap');
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--primary: #1F6FEB;
|
||||||
|
--secondary: #0F172A;
|
||||||
|
--accent: #F97316;
|
||||||
|
--bg: #F8FAFC;
|
||||||
|
--text: #1E293B;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background-color: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, .navbar-brand {
|
||||||
|
font-family: 'Manrope', sans-serif;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
background: linear-gradient(135deg, var(--secondary), var(--primary));
|
||||||
|
color: white;
|
||||||
|
padding: 100px 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom-left-radius: 40px;
|
||||||
|
border-bottom-right-radius: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05);
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card:hover {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-family: 'Manrope', sans-serif;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: var(--accent);
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.6rem 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user