Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7ca043bea | ||
|
|
c0b1bcc2be | ||
|
|
e17a272296 |
BIN
assets/images/banner.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
@ -18,7 +18,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
|||||||
load_dotenv(BASE_DIR.parent / ".env")
|
load_dotenv(BASE_DIR.parent / ".env")
|
||||||
|
|
||||||
SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "change-me")
|
SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "change-me")
|
||||||
DEBUG = os.getenv("DJANGO_DEBUG", "true").lower() == "true"
|
DEBUG = False
|
||||||
|
|
||||||
ALLOWED_HOSTS = [
|
ALLOWED_HOSTS = [
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
@ -155,6 +155,9 @@ STATICFILES_DIRS = [
|
|||||||
BASE_DIR / 'node_modules',
|
BASE_DIR / 'node_modules',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
MEDIA_URL = '/media/'
|
||||||
|
MEDIA_ROOT = BASE_DIR / 'media'
|
||||||
|
|
||||||
# Email
|
# Email
|
||||||
EMAIL_BACKEND = os.getenv(
|
EMAIL_BACKEND = os.getenv(
|
||||||
"EMAIL_BACKEND",
|
"EMAIL_BACKEND",
|
||||||
@ -180,3 +183,6 @@ 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'
|
||||||
|
|
||||||
|
LOGIN_REDIRECT_URL = 'dashboard'
|
||||||
|
LOGOUT_REDIRECT_URL = 'home'
|
||||||
|
|||||||
@ -24,6 +24,6 @@ urlpatterns = [
|
|||||||
path("", include("core.urls")),
|
path("", include("core.urls")),
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
# Always serve media files in dev environment
|
||||||
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from .models import Category, Shop, Product
|
||||||
|
|
||||||
# Register your models here.
|
@admin.register(Category)
|
||||||
|
class CategoryAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'slug')
|
||||||
|
prepopulated_fields = {'slug': ('name',)}
|
||||||
|
|
||||||
|
@admin.register(Shop)
|
||||||
|
class ShopAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'owner', 'whatsapp_number', 'created_at')
|
||||||
|
prepopulated_fields = {'slug': ('name',)}
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
if not obj.pk:
|
||||||
|
obj.owner = request.user
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
@admin.register(Product)
|
||||||
|
class ProductAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'shop', 'category', 'price', 'stock', 'is_active')
|
||||||
|
list_filter = ('shop', 'category', 'is_active')
|
||||||
|
prepopulated_fields = {'slug': ('name',)}
|
||||||
|
|||||||
58
core/migrations/0001_initial.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-17 04:38
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
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(blank=True, unique=True)),
|
||||||
|
('icon', models.CharField(default='fa-tag', help_text='FontAwesome icon class (e.g., fa-shopping-basket)', max_length=50)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Categories',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Shop',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('slug', models.SlugField(blank=True, unique=True)),
|
||||||
|
('description', models.TextField(blank=True)),
|
||||||
|
('logo', models.FileField(blank=True, null=True, upload_to='shop_logos/')),
|
||||||
|
('whatsapp_number', models.CharField(help_text='Format: 628123456789', max_length=20)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shops', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Product',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('slug', models.SlugField(blank=True, unique=True)),
|
||||||
|
('description', models.TextField()),
|
||||||
|
('price', models.DecimalField(decimal_places=2, max_digits=12)),
|
||||||
|
('image', models.FileField(blank=True, null=True, upload_to='product_images/')),
|
||||||
|
('stock', models.IntegerField(default=0)),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='products', to='core.category')),
|
||||||
|
('shop', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='core.shop')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-19 18:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='category',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(blank=True, max_length=150, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='product',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(blank=True, max_length=255, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='shop',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(blank=True, max_length=255, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
@ -1,3 +1,63 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.utils.text import slugify
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
# Create your models here.
|
class Category(models.Model):
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
slug = models.SlugField(max_length=150, unique=True, blank=True)
|
||||||
|
icon = models.CharField(max_length=50, help_text="FontAwesome icon class (e.g., fa-shopping-basket)", default="fa-tag")
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.slug:
|
||||||
|
self.slug = slugify(self.name)
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Categories"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.name)
|
||||||
|
|
||||||
|
class Shop(models.Model):
|
||||||
|
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='shops')
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
slug = models.SlugField(max_length=255, unique=True, blank=True)
|
||||||
|
description = models.TextField(blank=True)
|
||||||
|
logo = models.FileField(upload_to='shop_logos/', blank=True, null=True)
|
||||||
|
whatsapp_number = models.CharField(max_length=20, help_text="Format: 628123456789")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.slug:
|
||||||
|
self.slug = slugify(self.name)
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.name)
|
||||||
|
|
||||||
|
class Product(models.Model):
|
||||||
|
shop = models.ForeignKey(Shop, on_delete=models.CASCADE, related_name='products')
|
||||||
|
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='products')
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
slug = models.SlugField(max_length=255, unique=True, blank=True)
|
||||||
|
description = models.TextField()
|
||||||
|
price = models.DecimalField(max_digits=12, decimal_places=2)
|
||||||
|
image = models.FileField(upload_to='product_images/', blank=True, null=True)
|
||||||
|
stock = models.IntegerField(default=0)
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.slug:
|
||||||
|
self.slug = slugify(self.name)
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def whatsapp_link(self):
|
||||||
|
message = f"Halo {self.shop.name}, saya tertarik dengan produk *{self.name}* seharga Rp {self.price:,.0f}. Apakah masih tersedia?"
|
||||||
|
encoded_message = urllib.parse.quote(message)
|
||||||
|
return f"https://wa.me/{self.shop.whatsapp_number}?text={encoded_message}"
|
||||||
|
|||||||
@ -1,25 +1,97 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="id">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>{% block title %}Knowledge Base{% endblock %}</title>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
{% if project_description %}
|
<title>{% block title %}Marketplace UMKM Pemagarsari{% endblock %}</title>
|
||||||
<meta name="description" content="{{ project_description }}">
|
|
||||||
<meta property="og:description" content="{{ project_description }}">
|
<!-- Fonts -->
|
||||||
<meta property="twitter:description" content="{{ project_description }}">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
{% endif %}
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
{% if project_image_url %}
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@500;600;700&display=swap" rel="stylesheet">
|
||||||
<meta property="og:image" content="{{ project_image_url }}">
|
|
||||||
<meta property="twitter:image" content="{{ project_image_url }}">
|
<!-- Bootstrap 5 -->
|
||||||
{% endif %}
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<!-- FontAwesome -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
|
<link rel="stylesheet" href="{% static 'css/custom.css' %}">
|
||||||
|
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
{% block content %}{% endblock %}
|
<!-- Navbar -->
|
||||||
|
<nav class="navbar navbar-expand-lg sticky-top">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand fw-bold" href="{% url 'home' %}">
|
||||||
|
<i class="fa-solid fa-shop text-orange me-2"></i><span class="text-orange">UMKM</span> Pemagarsari
|
||||||
|
</a>
|
||||||
|
<button class="navbar-toggler border-0 shadow-none" 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 align-items-center">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link px-3" href="{% url 'home' %}">Beranda</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link px-3" href="{% url 'shop_list' %}">Toko</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link px-3" href="{% url 'home' %}#produk">Produk</a>
|
||||||
|
</li>
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<li class="nav-item dropdown ms-lg-3">
|
||||||
|
<a class="nav-link dropdown-toggle btn btn-light rounded-pill px-4" href="#" role="button" data-bs-toggle="dropdown">
|
||||||
|
<i class="fa-solid fa-user-circle me-2"></i>{{ user.username }}
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end border-0 shadow-sm rounded-3">
|
||||||
|
<li><a class="dropdown-item" href="{% url 'dashboard' %}">Dashboard</a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li>
|
||||||
|
<form action="{% url 'logout' %}" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="dropdown-item text-danger">Logout</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-item ms-lg-3">
|
||||||
|
<a class="btn btn-orange rounded-pill px-4" href="{% url 'login' %}">
|
||||||
|
<i class="fa-solid fa-right-to-bracket me-2"></i>Masuk
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="py-5 mt-5">
|
||||||
|
<div class="container text-center">
|
||||||
|
<h5 class="fw-bold mb-3">UMKM Desa Pemagarsari</h5>
|
||||||
|
<p class="text-muted small mb-4">Membangun ekonomi desa melalui digitalisasi marketplace mandiri.</p>
|
||||||
|
<div class="social-links mb-4">
|
||||||
|
<a href="#" class="mx-2"><i class="fa-brands fa-facebook text-muted"></i></a>
|
||||||
|
<a href="#" class="mx-2"><i class="fa-brands fa-instagram text-muted"></i></a>
|
||||||
|
<a href="#" class="mx-2"><i class="fa-brands fa-whatsapp text-muted"></i></a>
|
||||||
|
</div>
|
||||||
|
<hr class="w-25 mx-auto">
|
||||||
|
<p class="text-muted small mt-4">© 2026 Desa Pemagarsari. Powered by Flatlogic.</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
37
core/templates/core/auth/login.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Masuk - Marketplace UMKM Pemagarsari{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="row justify-content-center py-5">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="card border-0 rounded-4 shadow-sm p-4 p-md-5">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<h2 class="fw-bold">Selamat Datang</h2>
|
||||||
|
<p class="text-muted">Masuk untuk mengelola toko Anda</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Username</label>
|
||||||
|
<input type="text" name="username" class="form-control rounded-3 shadow-none py-2" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label fw-bold">Password</label>
|
||||||
|
<input type="password" name="password" class="form-control rounded-3 shadow-none py-2" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-orange w-100 rounded-pill py-3 mb-3">
|
||||||
|
Masuk Sekarang
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="text-center mt-4">
|
||||||
|
<p class="small text-muted mb-0">Belum punya akun? <br>Silakan hubungi Admin Desa untuk pendaftaran akun Penjual.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
167
core/templates/core/dashboard/index.html
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Dashboard Penjual - UMKM Pemagarsari{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-5">
|
||||||
|
<div>
|
||||||
|
<h1 class="fw-bold mb-1">Dashboard Penjual</h1>
|
||||||
|
<p class="text-muted mb-0">Kelola toko dan produk Anda di sini.</p>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a href="{% url 'shop_add' %}" class="btn btn-outline-orange rounded-pill px-4">
|
||||||
|
<i class="fa-solid fa-shop me-2"></i>Tambah Toko
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'product_add' %}" class="btn btn-orange rounded-pill px-4">
|
||||||
|
<i class="fa-solid fa-plus me-2"></i>Tambah Produk
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats -->
|
||||||
|
<div class="row g-4 mb-5">
|
||||||
|
<div class="col-md-6 col-lg-3">
|
||||||
|
<div class="card border-0 rounded-4 shadow-sm p-4">
|
||||||
|
<div class="d-flex align-items-center mb-2">
|
||||||
|
<div class="bg-orange bg-opacity-10 p-2 rounded-3 me-3">
|
||||||
|
<i class="fa-solid fa-shop text-orange"></i>
|
||||||
|
</div>
|
||||||
|
<span class="text-muted small">Toko Anda</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="fw-bold mb-0">{{ shops.count }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-lg-3">
|
||||||
|
<div class="card border-0 rounded-4 shadow-sm p-4">
|
||||||
|
<div class="d-flex align-items-center mb-2">
|
||||||
|
<div class="bg-primary bg-opacity-10 p-2 rounded-3 me-3">
|
||||||
|
<i class="fa-solid fa-box text-primary"></i>
|
||||||
|
</div>
|
||||||
|
<span class="text-muted small">Total Produk</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="fw-bold mb-0">{{ products.count }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Shops Table -->
|
||||||
|
<div class="card border-0 rounded-4 shadow-sm overflow-hidden mb-5">
|
||||||
|
<div class="card-header bg-white py-3 border-0">
|
||||||
|
<h5 class="fw-bold mb-0">Toko Saya</h5>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="bg-light text-muted small uppercase">
|
||||||
|
<tr>
|
||||||
|
<th class="ps-4">Toko</th>
|
||||||
|
<th>WhatsApp</th>
|
||||||
|
<th class="text-end pe-4">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for shop in shops %}
|
||||||
|
<tr>
|
||||||
|
<td class="ps-4">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="rounded-3 bg-light me-3 overflow-hidden" style="width: 40px; height: 40px;">
|
||||||
|
{% if shop.logo %}
|
||||||
|
<img src="{{ shop.logo.url }}" class="w-100 h-100 object-fit-cover">
|
||||||
|
{% else %}
|
||||||
|
<div class="w-100 h-100 d-flex align-items-center justify-content-center">
|
||||||
|
<i class="fa-solid fa-shop text-muted"></i>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<span class="fw-bold">{{ shop.name }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{{ shop.whatsapp_number }}</td>
|
||||||
|
<td class="text-end pe-4">
|
||||||
|
<a href="{% url 'shop_edit' shop.pk %}" class="btn btn-light btn-sm rounded-pill px-3">
|
||||||
|
<i class="fa-solid fa-pen-to-square me-1"></i> Edit
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-center py-4">
|
||||||
|
<p class="text-muted mb-0">Anda belum memiliki toko.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Products Table -->
|
||||||
|
<div class="card border-0 rounded-4 shadow-sm overflow-hidden">
|
||||||
|
<div class="card-header bg-white py-3 border-0">
|
||||||
|
<h5 class="fw-bold mb-0">Produk Saya</h5>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="bg-light text-muted small uppercase">
|
||||||
|
<tr>
|
||||||
|
<th class="ps-4">Produk</th>
|
||||||
|
<th>Toko</th>
|
||||||
|
<th>Kategori</th>
|
||||||
|
<th>Harga</th>
|
||||||
|
<th>Stok</th>
|
||||||
|
<th class="text-end pe-4">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for product in products %}
|
||||||
|
<tr>
|
||||||
|
<td class="ps-4">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="rounded-3 bg-light me-3 overflow-hidden" style="width: 48px; height: 48px;">
|
||||||
|
{% if product.image %}
|
||||||
|
<img src="{{ product.image.url }}" class="w-100 h-100 object-fit-cover">
|
||||||
|
{% else %}
|
||||||
|
<div class="w-100 h-100 d-flex align-items-center justify-content-center">
|
||||||
|
<i class="fa-solid fa-image text-muted"></i>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<span class="fw-bold">{{ product.name }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{{ product.shop.name }}</td>
|
||||||
|
<td><span class="badge bg-light text-dark rounded-pill">{{ product.category.name }}</span></td>
|
||||||
|
<td>Rp {{ product.price|floatformat:0 }}</td>
|
||||||
|
<td>{{ product.stock }}</td>
|
||||||
|
<td class="text-end pe-4">
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-light btn-sm rounded-circle shadow-none" type="button" data-bs-toggle="dropdown">
|
||||||
|
<i class="fa-solid fa-ellipsis-vertical"></i>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end border-0 shadow-sm rounded-3">
|
||||||
|
<li><a class="dropdown-item" href="{% url 'product_edit' product.pk %}"><i class="fa-solid fa-pen-to-square me-2 text-muted"></i> Edit</a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li>
|
||||||
|
<form action="{% url 'product_delete' product.pk %}" method="POST" onsubmit="return confirm('Yakin ingin menghapus produk ini?')">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="dropdown-item text-danger"><i class="fa-solid fa-trash me-2"></i> Hapus</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="text-center py-5">
|
||||||
|
<p class="text-muted mb-3">Anda belum menambahkan produk.</p>
|
||||||
|
<a href="{% url 'product_add' %}" class="btn btn-orange btn-sm rounded-pill px-4">Tambah Sekarang</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
74
core/templates/core/dashboard/product_form.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }} - Dashboard{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="card border-0 rounded-4 shadow-sm p-4 p-md-5">
|
||||||
|
<div class="d-flex align-items-center mb-4">
|
||||||
|
<a href="{% url 'dashboard' %}" class="btn btn-light rounded-circle me-3">
|
||||||
|
<i class="fa-solid fa-arrow-left"></i>
|
||||||
|
</a>
|
||||||
|
<h2 class="fw-bold mb-0">{{ title }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">Pilih Toko</label>
|
||||||
|
<select name="shop" class="form-select rounded-3 shadow-none" required>
|
||||||
|
{% for shop in shops %}
|
||||||
|
<option value="{{ shop.id }}" {% if product.shop.id == shop.id %}selected{% endif %}>{{ shop.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">Kategori</label>
|
||||||
|
<select name="category" class="form-select rounded-3 shadow-none" required>
|
||||||
|
{% for cat in categories %}
|
||||||
|
<option value="{{ cat.id }}" {% if product.category.id == cat.id %}selected{% endif %}>{{ cat.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label fw-bold">Nama Produk</label>
|
||||||
|
<input type="text" name="name" value="{{ product.name }}" class="form-control rounded-3 shadow-none" placeholder="Contoh: Kerupuk Jengkol Pedas" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label fw-bold">Deskripsi</label>
|
||||||
|
<textarea name="description" class="form-control rounded-3 shadow-none" rows="4" placeholder="Jelaskan keunggulan produk Anda..." required>{{ product.description }}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">Harga (Rp)</label>
|
||||||
|
<input type="number" name="price" value="{{ product.price|floatformat:0 }}" class="form-control rounded-3 shadow-none" placeholder="0" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">Stok</label>
|
||||||
|
<input type="number" name="stock" value="{{ product.stock|default:0 }}" class="form-control rounded-3 shadow-none" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label fw-bold">Foto Produk</label>
|
||||||
|
{% if product.image %}
|
||||||
|
<div class="mb-2">
|
||||||
|
<img src="{{ product.image.url }}" class="rounded-3" style="height: 100px;">
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<input type="file" name="image" class="form-control rounded-3 shadow-none">
|
||||||
|
<small class="text-muted">Kosongkan jika tidak ingin mengubah foto.</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 pt-3">
|
||||||
|
<button type="submit" class="btn btn-orange btn-lg w-100 rounded-pill py-3">
|
||||||
|
<i class="fa-solid fa-save me-2"></i>Simpan Produk
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
54
core/templates/core/dashboard/shop_form.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }} - Dashboard{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="card border-0 rounded-4 shadow-sm p-4 p-md-5">
|
||||||
|
<div class="d-flex align-items-center mb-4">
|
||||||
|
<a href="{% url 'dashboard' %}" class="btn btn-light rounded-circle me-3">
|
||||||
|
<i class="fa-solid fa-arrow-left"></i>
|
||||||
|
</a>
|
||||||
|
<h2 class="fw-bold mb-0">{{ title }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label fw-bold">Nama Toko</label>
|
||||||
|
<input type="text" name="name" value="{{ shop.name }}" class="form-control rounded-3 shadow-none" placeholder="Contoh: Dapur Sejahtera" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label fw-bold">Deskripsi Toko</label>
|
||||||
|
<textarea name="description" class="form-control rounded-3 shadow-none" rows="4" placeholder="Ceritakan tentang toko Anda..." required>{{ shop.description }}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label fw-bold">Nomor WhatsApp</label>
|
||||||
|
<input type="text" name="whatsapp_number" value="{{ shop.whatsapp_number }}" class="form-control rounded-3 shadow-none" placeholder="Contoh: 628123456789" required>
|
||||||
|
<small class="text-muted">Gunakan format internasional tanpa tanda plus (+). Contoh: 628123456789</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label fw-bold">Logo Toko (Opsional)</label>
|
||||||
|
{% if shop.logo %}
|
||||||
|
<div class="mb-2">
|
||||||
|
<img src="{{ shop.logo.url }}" class="rounded-3" style="height: 100px;">
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<input type="file" name="logo" class="form-control rounded-3 shadow-none">
|
||||||
|
</div>
|
||||||
|
<div class="col-12 pt-3">
|
||||||
|
<button type="submit" class="btn btn-orange btn-lg w-100 rounded-pill py-3">
|
||||||
|
<i class="fa-solid fa-save me-2"></i>Simpan Toko
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -1,145 +1,145 @@
|
|||||||
{% extends "base.html" %}
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
{% block title %}{{ project_name }}{% endblock %}
|
|
||||||
|
|
||||||
{% block head %}
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg-color-start: #6a11cb;
|
|
||||||
--bg-color-end: #2575fc;
|
|
||||||
--text-color: #ffffff;
|
|
||||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
|
||||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
|
||||||
color: var(--text-color);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><path d='M-10 10L110 10M10 -10L10 110' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
|
|
||||||
animation: bg-pan 20s linear infinite;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes bg-pan {
|
|
||||||
0% {
|
|
||||||
background-position: 0% 0%;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
background-position: 100% 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
background: var(--card-bg-color);
|
|
||||||
border: 1px solid var(--card-border-color);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 2.5rem 2rem;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: clamp(2.2rem, 3vw + 1.2rem, 3.2rem);
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0 0 1.2rem;
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
opacity: 0.92;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
margin: 1.5rem auto;
|
|
||||||
width: 56px;
|
|
||||||
height: 56px;
|
|
||||||
border: 4px solid rgba(255, 255, 255, 0.25);
|
|
||||||
border-top-color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.runtime code {
|
|
||||||
background: rgba(0, 0, 0, 0.25);
|
|
||||||
padding: 0.15rem 0.45rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
padding: 0;
|
|
||||||
margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1rem;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main>
|
<!-- Main Banner -->
|
||||||
<div class="card">
|
<div class="container-fluid px-0 mb-4">
|
||||||
<h1>Analyzing your requirements and generating your app…</h1>
|
<div class="banner-wrapper">
|
||||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
<img src="{% static 'images/banner.png' %}" alt="Banner Desa Pemagarsari" class="banner-img">
|
||||||
<span class="sr-only">Loading…</span>
|
<div class="position-absolute top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center bg-dark bg-opacity-25">
|
||||||
|
<h2 class="text-white fw-bold d-md-none">Desa Pemagarsari</h2>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p>
|
</div>
|
||||||
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
|
|
||||||
<p class="runtime">
|
<!-- Hero Section -->
|
||||||
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
|
<section class="hero-section">
|
||||||
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
|
<div class="container">
|
||||||
</p>
|
<div class="row align-items-center g-4">
|
||||||
</div>
|
<div class="col-lg-6 order-2 order-lg-1">
|
||||||
</main>
|
<div class="d-inline-block badge bg-orange mb-3 px-3 py-2 rounded-pill">Digitalisasi UMKM Desa</div>
|
||||||
<footer>
|
<h1 class="hero-title mb-4">Majukan Ekonomi Desa Pemagarsari</h1>
|
||||||
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
|
<p class="lead text-muted mb-4 text-center text-lg-start">Temukan produk unggulan dari warga desa kami. Pesan langsung via WhatsApp tanpa ribet, dukung produk lokal hari ini.</p>
|
||||||
</footer>
|
|
||||||
{% endblock %}
|
<!-- Search Bar -->
|
||||||
|
<form action="{% url 'home' %}" method="GET" class="mb-4">
|
||||||
|
<div class="search-box p-2 bg-white rounded-pill shadow-sm d-flex">
|
||||||
|
<input type="text" name="q" value="{{ query|default:'' }}" class="form-control border-0 bg-transparent px-3 px-md-4 shadow-none" placeholder="Cari produk...">
|
||||||
|
<button type="submit" class="btn btn-orange rounded-pill px-3 px-md-4">
|
||||||
|
<i class="fa-solid fa-magnifying-glass"></i><span class="d-none d-md-inline ms-2">Cari</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="d-flex flex-wrap gap-3 justify-content-center justify-content-lg-start">
|
||||||
|
<a href="#produk" class="btn btn-orange rounded-pill px-4">Jelajahi Produk</a>
|
||||||
|
{% if query or selected_category %}
|
||||||
|
<a href="{% url 'home' %}" class="btn btn-link text-muted text-decoration-none">Reset Filter</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6 order-1 order-lg-2">
|
||||||
|
<div class="position-relative p-2 p-md-4">
|
||||||
|
<div class="bg-orange rounded-circle position-absolute top-50 start-50 translate-middle opacity-10 d-none d-md-block" style="width: 100%; height: 100%; filter: blur(60px);"></div>
|
||||||
|
<div class="position-relative z-1 shadow-lg rounded-4 overflow-hidden border border-white border-4 transform-hover transition-all mx-auto" style="max-width: 500px; aspect-ratio: 4/3;">
|
||||||
|
<img src="https://images.unsplash.com/photo-1488459716781-31db52582fe9?auto=format&fit=crop&q=90&w=800" alt="Pasar Tradisional Desa" class="w-100 h-100 object-fit-cover">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Categories -->
|
||||||
|
<section id="kategori" class="py-5">
|
||||||
|
<div class="container">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h2 class="fw-bold">Kategori Populer</h2>
|
||||||
|
<p class="text-muted">Cari produk berdasarkan kategori</p>
|
||||||
|
</div>
|
||||||
|
<div class="row g-4 justify-content-center">
|
||||||
|
<div class="col-6 col-md-2">
|
||||||
|
<a href="{% url 'home' %}" class="category-card {% if not selected_category %}active{% endif %}">
|
||||||
|
<div class="category-icon">
|
||||||
|
<i class="fa-solid fa-border-all"></i>
|
||||||
|
</div>
|
||||||
|
<h6 class="fw-bold mb-0 text-dark">Semua</h6>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% for category in categories %}
|
||||||
|
<div class="col-6 col-md-2">
|
||||||
|
<a href="?category={{ category.slug }}{% if query %}&q={{ query }}{% endif %}" class="category-card {% if selected_category == category.slug %}active{% endif %}">
|
||||||
|
<div class="category-icon">
|
||||||
|
<i class="fa-solid {{ category.icon }}"></i>
|
||||||
|
</div>
|
||||||
|
<h6 class="fw-bold mb-0 text-dark">{{ category.name }}</h6>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Featured Products -->
|
||||||
|
<section id="produk" class="py-5 bg-light">
|
||||||
|
<div class="container">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h2 class="fw-bold">Produk Terbaru</h2>
|
||||||
|
<p class="text-muted">Produk unggulan dari para pengrajin dan pedagang desa</p>
|
||||||
|
</div>
|
||||||
|
<div class="row g-4">
|
||||||
|
{% for product in featured_products %}
|
||||||
|
<div class="col-12 col-sm-6 col-lg-3">
|
||||||
|
<div class="product-card">
|
||||||
|
<div class="product-img-wrapper">
|
||||||
|
{% if product.image %}
|
||||||
|
<img src="{{ product.image.url }}" alt="{{ product.name }}" class="img-fluid w-100 h-100 object-fit-cover">
|
||||||
|
{% else %}
|
||||||
|
<i class="fa-solid fa-image"></i>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<span class="shop-badge">{{ product.shop.name }}</span>
|
||||||
|
<small class="text-muted">{{ product.category.name }}</small>
|
||||||
|
</div>
|
||||||
|
<h5 class="fw-bold mb-3">
|
||||||
|
<a href="{% url 'product_detail' product.slug %}" class="text-decoration-none text-dark">{{ product.name }}</a>
|
||||||
|
</h5>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<span class="product-price">Rp {{ product.price|floatformat:0 }}</span>
|
||||||
|
<a href="{{ product.whatsapp_link }}" target="_blank" class="btn btn-whatsapp btn-sm px-3">
|
||||||
|
<i class="fa-brands fa-whatsapp me-2"></i>Pesan
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="col-12 text-center py-5">
|
||||||
|
<div class="bg-white p-5 rounded-4 shadow-sm">
|
||||||
|
<i class="fa-solid fa-box-open fa-3x text-muted mb-3"></i>
|
||||||
|
<p class="text-muted">Belum ada produk untuk ditampilkan.</p>
|
||||||
|
<a href="/admin/core/product/add/" class="btn btn-orange mt-3">Tambah Produk Pertama</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Call to Action -->
|
||||||
|
<section class="py-5">
|
||||||
|
<div class="container">
|
||||||
|
<div class="p-5 rounded-4 text-white text-center position-relative overflow-hidden shadow-lg" style="background: linear-gradient(135deg, rgba(30, 58, 138, 0.85) 0%, rgba(249, 115, 22, 0.75) 100%), url('https://images.unsplash.com/photo-1516253593875-bd7ba052fbc5?auto=format&fit=crop&q=90&w=1200'); background-size: cover; background-position: center;">
|
||||||
|
<div class="position-relative z-1">
|
||||||
|
<h2 class="fw-bold mb-3">Punya Usaha di Desa Pemagarsari?</h2>
|
||||||
|
<p class="mb-4 opacity-75">Daftarkan toko Anda dan mulai jualan online hari ini. Mudah, gratis, dan langsung ke WhatsApp!</p>
|
||||||
|
<div class="d-flex justify-content-center gap-3">
|
||||||
|
<a href="{% if user.is_authenticated %}{% url 'dashboard' %}{% else %}{% url 'login' %}{% endif %}" class="btn btn-orange btn-lg px-5 rounded-pill shadow-lg">Mulai Jualan Sekarang</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
105
core/templates/core/product_detail.html
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}{{ product.name }} - UMKM Pemagarsari{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="py-5">
|
||||||
|
<div class="container">
|
||||||
|
<nav aria-label="breadcrumb" class="mb-4">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'home' %}" class="text-decoration-none text-orange">Beranda</a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">{{ product.name }}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="row g-5">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="bg-white rounded-4 p-2 shadow-sm">
|
||||||
|
<div class="product-img-wrapper rounded-3" style="height: 450px;">
|
||||||
|
{% if product.image %}
|
||||||
|
<img src="{{ product.image.url }}" alt="{{ product.name }}" class="img-fluid w-100 h-100 object-fit-cover rounded-3">
|
||||||
|
{% else %}
|
||||||
|
<i class="fa-solid fa-image fa-4x"></i>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="ps-lg-4">
|
||||||
|
<span class="badge bg-orange mb-2 px-3 py-2 rounded-pill">{{ product.category.name }}</span>
|
||||||
|
<h1 class="fw-bold mb-3">{{ product.name }}</h1>
|
||||||
|
<div class="d-flex align-items-center mb-4">
|
||||||
|
<div class="bg-light p-2 rounded-3 me-3">
|
||||||
|
<i class="fa-solid fa-shop text-orange"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="mb-0 small text-muted">Dijual oleh</p>
|
||||||
|
<h6 class="fw-bold mb-0">{{ product.shop.name }}</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="product-price mb-4" style="font-size: 2.5rem;">Rp {{ product.price|floatformat:0 }}</h2>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<h6 class="fw-bold">Deskripsi Produk</h6>
|
||||||
|
<p class="text-muted">{{ product.description|linebreaks }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card border-0 bg-light rounded-4 p-4 mb-4">
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<i class="fa-solid fa-circle-info text-orange me-2"></i>
|
||||||
|
<h6 class="fw-bold mb-0">Cara Pemesanan</h6>
|
||||||
|
</div>
|
||||||
|
<p class="small text-muted mb-0">Klik tombol di bawah untuk terhubung langsung dengan WhatsApp penjual. Pesan otomatis akan terisi dengan detail produk ini.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid">
|
||||||
|
<a href="{{ product.whatsapp_link }}" target="_blank" class="btn btn-whatsapp btn-lg py-3 rounded-pill">
|
||||||
|
<i class="fa-brands fa-whatsapp me-2"></i>Pesan via WhatsApp Sekarang
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Related Products Placeholder -->
|
||||||
|
<section class="py-5 bg-light mt-5">
|
||||||
|
<div class="container text-center">
|
||||||
|
<h4 class="fw-bold mb-4">Dukung UMKM Lainnya</h4>
|
||||||
|
<div class="row g-4 opacity-50">
|
||||||
|
<!-- Simulated related products -->
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="bg-white p-4 rounded-4 shadow-sm">
|
||||||
|
<div class="bg-light mb-3 rounded" style="height: 150px;"></div>
|
||||||
|
<div class="bg-light w-75 h-25 mb-2 mx-auto"></div>
|
||||||
|
<div class="bg-light w-50 h-25 mx-auto"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 d-none d-md-block">
|
||||||
|
<div class="bg-white p-4 rounded-4 shadow-sm">
|
||||||
|
<div class="bg-light mb-3 rounded" style="height: 150px;"></div>
|
||||||
|
<div class="bg-light w-75 h-25 mb-2 mx-auto"></div>
|
||||||
|
<div class="bg-light w-50 h-25 mx-auto"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 d-none d-md-block">
|
||||||
|
<div class="bg-white p-4 rounded-4 shadow-sm">
|
||||||
|
<div class="bg-light mb-3 rounded" style="height: 150px;"></div>
|
||||||
|
<div class="bg-light w-75 h-25 mb-2 mx-auto"></div>
|
||||||
|
<div class="bg-light w-50 h-25 mx-auto"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 d-none d-md-block">
|
||||||
|
<div class="bg-white p-4 rounded-4 shadow-sm">
|
||||||
|
<div class="bg-light mb-3 rounded" style="height: 150px;"></div>
|
||||||
|
<div class="bg-light w-75 h-25 mb-2 mx-auto"></div>
|
||||||
|
<div class="bg-light w-50 h-25 mx-auto"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
83
core/templates/core/shop_detail.html
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}{{ shop.name }} - UMKM Pemagarsari{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="py-5">
|
||||||
|
<div class="container">
|
||||||
|
<!-- Shop Header -->
|
||||||
|
<div class="bg-white rounded-4 shadow-sm p-4 p-md-5 mb-5 overflow-hidden position-relative">
|
||||||
|
<div class="position-absolute top-0 end-0 p-4 opacity-10">
|
||||||
|
<i class="fa-solid fa-shop fa-8x"></i>
|
||||||
|
</div>
|
||||||
|
<div class="row align-items-center position-relative z-1">
|
||||||
|
<div class="col-md-auto mb-4 mb-md-0 text-center">
|
||||||
|
<div class="bg-orange rounded-circle d-flex align-items-center justify-content-center text-white mx-auto" style="width: 120px; height: 120px; overflow: hidden;">
|
||||||
|
{% if shop.logo %}
|
||||||
|
<img src="{{ shop.logo.url }}" alt="{{ shop.name }}" class="img-fluid w-100 h-100 object-fit-cover">
|
||||||
|
{% else %}
|
||||||
|
<i class="fa-solid fa-shop fa-3x"></i>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md ps-md-4">
|
||||||
|
<h1 class="fw-bold mb-2">{{ shop.name }}</h1>
|
||||||
|
<p class="text-muted mb-4">{{ shop.description }}</p>
|
||||||
|
<div class="d-flex flex-wrap gap-3">
|
||||||
|
<div class="d-flex align-items-center bg-light px-3 py-2 rounded-pill">
|
||||||
|
<i class="fa-brands fa-whatsapp text-success me-2"></i>
|
||||||
|
<span class="fw-bold">{{ shop.whatsapp_number }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center bg-light px-3 py-2 rounded-pill">
|
||||||
|
<i class="fa-solid fa-box text-orange me-2"></i>
|
||||||
|
<span>{{ products.count }} Produk</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-auto mt-4 mt-md-0">
|
||||||
|
<a href="https://wa.me/{{ shop.whatsapp_number }}" target="_blank" class="btn btn-whatsapp rounded-pill px-4 py-2 w-100">
|
||||||
|
<i class="fa-brands fa-whatsapp me-2"></i>Chat Penjual
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Shop Products -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h4 class="fw-bold mb-0">Produk dari {{ shop.name }}</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-4">
|
||||||
|
{% for product in products %}
|
||||||
|
<div class="col-12 col-sm-6 col-lg-3">
|
||||||
|
<div class="product-card">
|
||||||
|
<div class="product-img-wrapper">
|
||||||
|
{% if product.image %}
|
||||||
|
<img src="{{ product.image.url }}" alt="{{ product.name }}" class="img-fluid w-100 h-100 object-fit-cover">
|
||||||
|
{% else %}
|
||||||
|
<i class="fa-solid fa-image"></i>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="p-4">
|
||||||
|
<small class="text-muted d-block mb-2">{{ product.category.name }}</small>
|
||||||
|
<h5 class="fw-bold mb-3">
|
||||||
|
<a href="{% url 'product_detail' product.slug %}" class="text-decoration-none text-dark">{{ product.name }}</a>
|
||||||
|
</h5>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<span class="product-price">Rp {{ product.price|floatformat:0 }}</span>
|
||||||
|
<a href="{{ product.whatsapp_link }}" target="_blank" class="btn btn-whatsapp btn-sm px-3">
|
||||||
|
<i class="fa-brands fa-whatsapp me-2"></i>Pesan
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="col-12 text-center py-5">
|
||||||
|
<p class="text-muted">Toko ini belum menambahkan produk.</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
47
core/templates/core/shop_list.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Daftar Toko UMKM - Pemagarsari{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="py-5 bg-light">
|
||||||
|
<div class="container">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h1 class="fw-bold">Para Pelaku UMKM</h1>
|
||||||
|
<p class="text-muted">Dukung usaha warga Desa Pemagarsari dengan berbelanja langsung dari toko mereka.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-4">
|
||||||
|
{% for shop in shops %}
|
||||||
|
<div class="col-md-6 col-lg-4">
|
||||||
|
<div class="card border-0 rounded-4 shadow-sm h-100 overflow-hidden">
|
||||||
|
<div class="row g-0 h-100">
|
||||||
|
<div class="col-4 bg-orange d-flex align-items-center justify-content-center text-white">
|
||||||
|
{% if shop.logo %}
|
||||||
|
<img src="{{ shop.logo.url }}" alt="{{ shop.name }}" class="img-fluid w-100 h-100 object-fit-cover">
|
||||||
|
{% else %}
|
||||||
|
<i class="fa-solid fa-shop fa-3x opacity-50"></i>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<h5 class="fw-bold mb-2">{{ shop.name }}</h5>
|
||||||
|
<p class="text-muted small mb-3 text-truncate-2">{{ shop.description|default:"Pelaku UMKM Desa Pemagarsari" }}</p>
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<i class="fa-brands fa-whatsapp text-success me-2"></i>
|
||||||
|
<span class="small text-muted">{{ shop.whatsapp_number }}</span>
|
||||||
|
</div>
|
||||||
|
<a href="{% url 'shop_detail' shop.slug %}" class="btn btn-outline-orange btn-sm rounded-pill px-4">Kunjungi Toko</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="col-12 text-center py-5">
|
||||||
|
<p class="text-muted">Belum ada toko yang terdaftar.</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
25
core/urls.py
@ -1,7 +1,26 @@
|
|||||||
from django.urls import path
|
from django.urls import path, include
|
||||||
|
from django.contrib.auth import views as auth_views
|
||||||
from .views import home
|
from .views import (
|
||||||
|
home, product_detail, shop_list, shop_detail,
|
||||||
|
dashboard, shop_add, shop_edit,
|
||||||
|
product_add, product_edit, product_delete
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", home, name="home"),
|
path("", home, name="home"),
|
||||||
|
path("shops/", shop_list, name="shop_list"),
|
||||||
|
path("shop/<slug:slug>/", shop_detail, name="shop_detail"),
|
||||||
|
path("product/<slug:slug>/", product_detail, name="product_detail"),
|
||||||
|
|
||||||
|
# Dashboard
|
||||||
|
path("dashboard/", dashboard, name="dashboard"),
|
||||||
|
path("dashboard/shop/add/", shop_add, name="shop_add"),
|
||||||
|
path("dashboard/shop/edit/<int:pk>/", shop_edit, name="shop_edit"),
|
||||||
|
path("dashboard/product/add/", product_add, name="product_add"),
|
||||||
|
path("dashboard/product/edit/<int:pk>/", product_edit, name="product_edit"),
|
||||||
|
path("dashboard/product/delete/<int:pk>/", product_delete, name="product_delete"),
|
||||||
|
|
||||||
|
# Auth
|
||||||
|
path("login/", auth_views.LoginView.as_view(template_name="core/auth/login.html"), name="login"),
|
||||||
|
path("logout/", auth_views.LogoutView.as_view(next_page="home"), name="logout"),
|
||||||
]
|
]
|
||||||
|
|||||||
180
core/views.py
@ -2,24 +2,174 @@ import os
|
|||||||
import platform
|
import platform
|
||||||
|
|
||||||
from django import get_version as django_version
|
from django import get_version as django_version
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from .models import Category, Shop, Product
|
||||||
|
|
||||||
def home(request):
|
def home(request):
|
||||||
"""Render the landing screen with loader and environment details."""
|
"""Render the marketplace landing page with search and filter."""
|
||||||
host_name = request.get_host().lower()
|
categories = Category.objects.all()
|
||||||
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
|
|
||||||
now = timezone.now()
|
# Get query parameters
|
||||||
|
query = request.GET.get('q', '')
|
||||||
|
category_slug = request.GET.get('category', '')
|
||||||
|
|
||||||
|
products = Product.objects.filter(is_active=True).order_by('-created_at')
|
||||||
|
|
||||||
|
if query:
|
||||||
|
products = products.filter(name__icontains=query)
|
||||||
|
|
||||||
|
if category_slug:
|
||||||
|
products = products.filter(category__slug=category_slug)
|
||||||
|
|
||||||
|
featured_products = products[:12] # Show more products on home
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"project_name": "New Style",
|
"categories": categories,
|
||||||
"agent_brand": agent_brand,
|
"featured_products": featured_products,
|
||||||
"django_version": django_version(),
|
"query": query,
|
||||||
"python_version": platform.python_version(),
|
"selected_category": category_slug,
|
||||||
"current_time": now,
|
"current_time": timezone.now(),
|
||||||
"host_name": host_name,
|
|
||||||
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
|
|
||||||
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
|
|
||||||
}
|
}
|
||||||
return render(request, "core/index.html", context)
|
return render(request, "core/index.html", context)
|
||||||
|
|
||||||
|
def product_detail(request, slug):
|
||||||
|
"""Render the product detail page."""
|
||||||
|
product = get_object_or_404(Product, slug=slug, is_active=True)
|
||||||
|
return render(request, "core/product_detail.html", {"product": product})
|
||||||
|
|
||||||
|
def shop_list(request):
|
||||||
|
"""Render a list of all shops."""
|
||||||
|
shops = Shop.objects.all().order_by('name')
|
||||||
|
return render(request, "core/shop_list.html", {"shops": shops})
|
||||||
|
|
||||||
|
def shop_detail(request, slug):
|
||||||
|
"""Render a specific shop's page with its products."""
|
||||||
|
shop = get_object_or_404(Shop, slug=slug)
|
||||||
|
products = Product.objects.filter(shop=shop, is_active=True)
|
||||||
|
return render(request, "core/shop_detail.html", {"shop": shop, "products": products})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def dashboard(request):
|
||||||
|
"""Seller dashboard showing their shops and products."""
|
||||||
|
shops = Shop.objects.filter(owner=request.user)
|
||||||
|
products = Product.objects.filter(shop__owner=request.user).order_by('-created_at')
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"shops": shops,
|
||||||
|
"products": products,
|
||||||
|
}
|
||||||
|
return render(request, "core/dashboard/index.html", context)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def shop_add(request):
|
||||||
|
"""Add a new shop."""
|
||||||
|
if request.method == "POST":
|
||||||
|
name = request.POST.get('name')
|
||||||
|
description = request.POST.get('description')
|
||||||
|
whatsapp_number = request.POST.get('whatsapp_number')
|
||||||
|
logo = request.FILES.get('logo')
|
||||||
|
|
||||||
|
Shop.objects.create(
|
||||||
|
owner=request.user,
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
whatsapp_number=whatsapp_number,
|
||||||
|
logo=logo
|
||||||
|
)
|
||||||
|
return redirect('dashboard')
|
||||||
|
|
||||||
|
return render(request, "core/dashboard/shop_form.html", {
|
||||||
|
"title": "Tambah Toko Baru"
|
||||||
|
})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def shop_edit(request, pk):
|
||||||
|
"""Edit an existing shop."""
|
||||||
|
shop = get_object_or_404(Shop, pk=pk, owner=request.user)
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
shop.name = request.POST.get('name')
|
||||||
|
shop.description = request.POST.get('description')
|
||||||
|
shop.whatsapp_number = request.POST.get('whatsapp_number')
|
||||||
|
if request.FILES.get('logo'):
|
||||||
|
shop.logo = request.FILES.get('logo')
|
||||||
|
|
||||||
|
shop.save()
|
||||||
|
return redirect('dashboard')
|
||||||
|
|
||||||
|
return render(request, "core/dashboard/shop_form.html", {
|
||||||
|
"shop": shop,
|
||||||
|
"title": "Edit Toko"
|
||||||
|
})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def product_add(request):
|
||||||
|
"""Add a new product."""
|
||||||
|
shops = Shop.objects.filter(owner=request.user)
|
||||||
|
categories = Category.objects.all()
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
shop_id = request.POST.get('shop')
|
||||||
|
category_id = request.POST.get('category')
|
||||||
|
name = request.POST.get('name')
|
||||||
|
description = request.POST.get('description')
|
||||||
|
price = request.POST.get('price')
|
||||||
|
stock = request.POST.get('stock')
|
||||||
|
image = request.FILES.get('image')
|
||||||
|
|
||||||
|
shop = get_object_or_404(Shop, id=shop_id, owner=request.user)
|
||||||
|
category = get_object_or_404(Category, id=category_id)
|
||||||
|
|
||||||
|
Product.objects.create(
|
||||||
|
shop=shop,
|
||||||
|
category=category,
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
price=price,
|
||||||
|
stock=stock,
|
||||||
|
image=image
|
||||||
|
)
|
||||||
|
return redirect('dashboard')
|
||||||
|
|
||||||
|
return render(request, "core/dashboard/product_form.html", {
|
||||||
|
"shops": shops,
|
||||||
|
"categories": categories,
|
||||||
|
"title": "Tambah Produk Baru"
|
||||||
|
})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def product_edit(request, pk):
|
||||||
|
"""Edit an existing product."""
|
||||||
|
product = get_object_or_404(Product, pk=pk, shop__owner=request.user)
|
||||||
|
shops = Shop.objects.filter(owner=request.user)
|
||||||
|
categories = Category.objects.all()
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
product.shop = get_object_or_404(Shop, id=request.POST.get('shop'), owner=request.user)
|
||||||
|
product.category = get_object_or_404(Category, id=request.POST.get('category'))
|
||||||
|
product.name = request.POST.get('name')
|
||||||
|
product.description = request.POST.get('description')
|
||||||
|
product.price = request.POST.get('price')
|
||||||
|
product.stock = request.POST.get('stock')
|
||||||
|
if request.FILES.get('image'):
|
||||||
|
product.image = request.FILES.get('image')
|
||||||
|
|
||||||
|
product.save()
|
||||||
|
return redirect('dashboard')
|
||||||
|
|
||||||
|
return render(request, "core/dashboard/product_form.html", {
|
||||||
|
"product": product,
|
||||||
|
"shops": shops,
|
||||||
|
"categories": categories,
|
||||||
|
"title": "Edit Produk"
|
||||||
|
})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def product_delete(request, pk):
|
||||||
|
"""Delete a product."""
|
||||||
|
product = get_object_or_404(Product, pk=pk, shop__owner=request.user)
|
||||||
|
if request.method == "POST":
|
||||||
|
product.delete()
|
||||||
|
return redirect('dashboard')
|
||||||
|
|||||||
BIN
media/product_images/7cVv0a78syo3XkKZFUGKtRZIx9PcIRI1JDfFekt5_ahMb402.jpg
Executable file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
media/product_images/Golden_banana_chips_in_garden_sunshine_1.png
Executable file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
media/product_images/Golden_banana_chips_in_wooden_bowl.png
Executable file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
media/product_images/Jajanan_Pasar_Masakan_Nusantara.png
Executable file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
media/product_images/Laundry_Ibu_Tini_logo_design_1.png
Executable file
|
After Width: | Height: | Size: 307 KiB |
BIN
media/product_images/Masakan_Nusantara_Ibu_Tini.png
Executable file
|
After Width: | Height: | Size: 398 KiB |
BIN
media/shop_logos/7819e47b-9455-4740-bb47-199007a1b8a4_fM25NsS.png
Executable file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
media/shop_logos/Kripik_Pisang_logo_design.png
Executable file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
media/shop_logos/Logo_Masakan_Nusantara_Ibu_Tini.png
Executable file
|
After Width: | Height: | Size: 3.0 MiB |
BIN
product_images/7cVv0a78syo3XkKZFUGKtRZIx9PcIRI1JDfFekt5.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 70 KiB |
BIN
product_images/c1f6b9fe04af42e90ee4053e6112b82edb94a6d7.webp
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
shop_logos/7819e47b-9455-4740-bb47-199007a1b8a4.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
shop_logos/7819e47b-9455-4740-bb47-199007a1b8a4_fM25NsS.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
332
static/css/custom.css
Normal file → Executable file
@ -1,4 +1,330 @@
|
|||||||
/* Custom styles for the application */
|
:root {
|
||||||
body {
|
--primary-orange: #F97316; /* Orange 500 */
|
||||||
font-family: system-ui, -apple-system, sans-serif;
|
--dark-orange: #EA580C; /* Orange 600 */
|
||||||
|
--primary-blue: #2563EB; /* Blue 600 */
|
||||||
|
--dark-blue: #1E40AF; /* Blue 800 */
|
||||||
|
--deep-blue: #1E3A8A; /* Blue 900 */
|
||||||
|
|
||||||
|
/* Compatibility Mapping */
|
||||||
|
--emerald-primary: var(--primary-orange);
|
||||||
|
--emerald-dark: var(--dark-orange);
|
||||||
|
--forest-deep: var(--deep-blue);
|
||||||
|
|
||||||
|
--amber-warm: #F59E0B;
|
||||||
|
--glass-bg: rgba(255, 255, 255, 0.9);
|
||||||
|
--soft-shadow: 0 10px 30px rgba(30, 58, 138, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background-color: #F0FDF4; /* Hijau Muda */
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, .navbar-brand {
|
||||||
|
font-family: 'Poppins', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Orange Styles */
|
||||||
|
.text-orange, .text-emerald {
|
||||||
|
color: var(--primary-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-orange, .bg-emerald {
|
||||||
|
background-color: var(--primary-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-orange, .btn-emerald {
|
||||||
|
background-color: var(--primary-orange);
|
||||||
|
border-color: var(--primary-orange);
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-orange:hover, .btn-emerald:hover {
|
||||||
|
background-color: var(--dark-orange);
|
||||||
|
border-color: var(--dark-orange);
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(249, 115, 22, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-orange, .btn-outline-emerald {
|
||||||
|
color: var(--primary-orange);
|
||||||
|
border: 2px solid var(--primary-orange);
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-orange:hover, .btn-outline-emerald:hover {
|
||||||
|
background-color: var(--primary-orange);
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Blue Styles */
|
||||||
|
.text-blue {
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-blue {
|
||||||
|
background-color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-blue {
|
||||||
|
background-color: var(--primary-blue);
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-blue:hover {
|
||||||
|
background-color: var(--dark-blue);
|
||||||
|
border-color: var(--dark-blue);
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(37, 99, 235, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
background: var(--glass-bg);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-bottom: 1px solid rgba(30, 58, 138, 0.05);
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--deep-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
color: var(--deep-blue);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover {
|
||||||
|
color: var(--primary-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hero Section */
|
||||||
|
.hero-section {
|
||||||
|
padding: 100px 0;
|
||||||
|
background: linear-gradient(135deg, #DCFCE7 0%, #F0FDF4 100%);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-section::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -10%;
|
||||||
|
right: -5%;
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
background: radial-gradient(circle, rgba(34, 197, 94, 0.1) 0%, rgba(255, 255, 255, 0) 70%);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: var(--deep-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards */
|
||||||
|
.category-card {
|
||||||
|
background: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: var(--soft-shadow);
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 15px 40px rgba(30, 58, 138, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-icon {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
background: #fff7ed;
|
||||||
|
color: var(--primary-orange);
|
||||||
|
border-radius: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto 1.5rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card {
|
||||||
|
background: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: var(--soft-shadow);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 15px 40px rgba(30, 58, 138, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-img-wrapper {
|
||||||
|
height: 220px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-img-wrapper img {
|
||||||
|
transition: transform 0.5s ease;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card:hover .product-img-wrapper img {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-img-wrapper i {
|
||||||
|
font-size: 4rem;
|
||||||
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price {
|
||||||
|
color: var(--primary-orange);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shop-badge {
|
||||||
|
background: #eff6ff;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 100px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WhatsApp Button */
|
||||||
|
.btn-whatsapp {
|
||||||
|
background-color: #25D366;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-whatsapp:hover {
|
||||||
|
background-color: #128C7E;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transform-hover {
|
||||||
|
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
animation: floating 6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes floating {
|
||||||
|
0% { transform: translateY(0px); }
|
||||||
|
50% { transform: translateY(-15px); }
|
||||||
|
100% { transform: translateY(0px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.transform-hover:hover {
|
||||||
|
animation-play-state: paused;
|
||||||
|
transform: scale(1.03) translateY(-5px);
|
||||||
|
box-shadow: 0 30px 60px -12px rgba(249, 115, 22, 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition-all {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search Box */
|
||||||
|
.search-box {
|
||||||
|
border: 1px solid rgba(30, 58, 138, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box:focus-within {
|
||||||
|
border-color: var(--primary-orange);
|
||||||
|
box-shadow: 0 0 0 4px rgba(249, 115, 22, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Categories Active State */
|
||||||
|
.category-card.active {
|
||||||
|
background: var(--deep-blue);
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-card.active .category-icon {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-card.active h6 {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background-color: #F0FDF4;
|
||||||
|
border-top: 1px solid rgba(34, 197, 94, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.hero-title {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.hero-section {
|
||||||
|
padding: 60px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.banner-wrapper {
|
||||||
|
height: 200px !important;
|
||||||
|
}
|
||||||
|
.category-card {
|
||||||
|
padding: 1.2rem;
|
||||||
|
}
|
||||||
|
.category-icon {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utilities */
|
||||||
|
.banner-wrapper {
|
||||||
|
height: 400px;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.object-fit-cover {
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
static/images/banner.png
Executable file
|
After Width: | Height: | Size: 144 KiB |
@ -1,21 +1,330 @@
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
--bg-color-start: #6a11cb;
|
--primary-orange: #F97316; /* Orange 500 */
|
||||||
--bg-color-end: #2575fc;
|
--dark-orange: #EA580C; /* Orange 600 */
|
||||||
--text-color: #ffffff;
|
--primary-blue: #2563EB; /* Blue 600 */
|
||||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
--dark-blue: #1E40AF; /* Blue 800 */
|
||||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
--deep-blue: #1E3A8A; /* Blue 900 */
|
||||||
|
|
||||||
|
/* Compatibility Mapping */
|
||||||
|
--emerald-primary: var(--primary-orange);
|
||||||
|
--emerald-dark: var(--dark-orange);
|
||||||
|
--forest-deep: var(--deep-blue);
|
||||||
|
|
||||||
|
--amber-warm: #F59E0B;
|
||||||
|
--glass-bg: rgba(255, 255, 255, 0.9);
|
||||||
|
--soft-shadow: 0 10px 30px rgba(30, 58, 138, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
|
||||||
font-family: 'Inter', sans-serif;
|
font-family: 'Inter', sans-serif;
|
||||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
background-color: #F0FDF4; /* Hijau Muda */
|
||||||
color: var(--text-color);
|
color: #1e293b;
|
||||||
display: flex;
|
}
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
h1, h2, h3, h4, .navbar-brand {
|
||||||
min-height: 100vh;
|
font-family: 'Poppins', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Orange Styles */
|
||||||
|
.text-orange, .text-emerald {
|
||||||
|
color: var(--primary-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-orange, .bg-emerald {
|
||||||
|
background-color: var(--primary-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-orange, .btn-emerald {
|
||||||
|
background-color: var(--primary-orange);
|
||||||
|
border-color: var(--primary-orange);
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-orange:hover, .btn-emerald:hover {
|
||||||
|
background-color: var(--dark-orange);
|
||||||
|
border-color: var(--dark-orange);
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(249, 115, 22, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-orange, .btn-outline-emerald {
|
||||||
|
color: var(--primary-orange);
|
||||||
|
border: 2px solid var(--primary-orange);
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-orange:hover, .btn-outline-emerald:hover {
|
||||||
|
background-color: var(--primary-orange);
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Blue Styles */
|
||||||
|
.text-blue {
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-blue {
|
||||||
|
background-color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-blue {
|
||||||
|
background-color: var(--primary-blue);
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-blue:hover {
|
||||||
|
background-color: var(--dark-blue);
|
||||||
|
border-color: var(--dark-blue);
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(37, 99, 235, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
background: var(--glass-bg);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-bottom: 1px solid rgba(30, 58, 138, 0.05);
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--deep-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
color: var(--deep-blue);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover {
|
||||||
|
color: var(--primary-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hero Section */
|
||||||
|
.hero-section {
|
||||||
|
padding: 100px 0;
|
||||||
|
background: linear-gradient(135deg, #DCFCE7 0%, #F0FDF4 100%);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-section::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -10%;
|
||||||
|
right: -5%;
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
background: radial-gradient(circle, rgba(34, 197, 94, 0.1) 0%, rgba(255, 255, 255, 0) 70%);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: var(--deep-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards */
|
||||||
|
.category-card {
|
||||||
|
background: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: var(--soft-shadow);
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 15px 40px rgba(30, 58, 138, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-icon {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
background: #fff7ed;
|
||||||
|
color: var(--primary-orange);
|
||||||
|
border-radius: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto 1.5rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card {
|
||||||
|
background: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: var(--soft-shadow);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 15px 40px rgba(30, 58, 138, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-img-wrapper {
|
||||||
|
height: 220px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.product-img-wrapper img {
|
||||||
|
transition: transform 0.5s ease;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card:hover .product-img-wrapper img {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-img-wrapper i {
|
||||||
|
font-size: 4rem;
|
||||||
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price {
|
||||||
|
color: var(--primary-orange);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shop-badge {
|
||||||
|
background: #eff6ff;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 100px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WhatsApp Button */
|
||||||
|
.btn-whatsapp {
|
||||||
|
background-color: #25D366;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-whatsapp:hover {
|
||||||
|
background-color: #128C7E;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transform-hover {
|
||||||
|
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
animation: floating 6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes floating {
|
||||||
|
0% { transform: translateY(0px); }
|
||||||
|
50% { transform: translateY(-15px); }
|
||||||
|
100% { transform: translateY(0px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.transform-hover:hover {
|
||||||
|
animation-play-state: paused;
|
||||||
|
transform: scale(1.03) translateY(-5px);
|
||||||
|
box-shadow: 0 30px 60px -12px rgba(249, 115, 22, 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition-all {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search Box */
|
||||||
|
.search-box {
|
||||||
|
border: 1px solid rgba(30, 58, 138, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box:focus-within {
|
||||||
|
border-color: var(--primary-orange);
|
||||||
|
box-shadow: 0 0 0 4px rgba(249, 115, 22, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Categories Active State */
|
||||||
|
.category-card.active {
|
||||||
|
background: var(--deep-blue);
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-card.active .category-icon {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-card.active h6 {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background-color: #F0FDF4;
|
||||||
|
border-top: 1px solid rgba(34, 197, 94, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.hero-title {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.hero-section {
|
||||||
|
padding: 60px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.banner-wrapper {
|
||||||
|
height: 200px !important;
|
||||||
|
}
|
||||||
|
.category-card {
|
||||||
|
padding: 1.2rem;
|
||||||
|
}
|
||||||
|
.category-icon {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utilities */
|
||||||
|
.banner-wrapper {
|
||||||
|
height: 400px;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.object-fit-cover {
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|||||||
BIN
staticfiles/images/banner.png
Normal file
|
After Width: | Height: | Size: 144 KiB |