Compare commits

..

2 Commits

Author SHA1 Message Date
Flatlogic Bot
901f6146fe Edit config/settings.py via Editor 2026-02-07 10:56:03 +00:00
Flatlogic Bot
6aa80a21bb Store.1 2026-02-07 10:18:20 +00:00
31 changed files with 981 additions and 193 deletions

View File

@ -23,18 +23,18 @@ DEBUG = os.getenv("DJANGO_DEBUG", "true").lower() == "true"
ALLOWED_HOSTS = [ ALLOWED_HOSTS = [
"127.0.0.1", "127.0.0.1",
"localhost", "localhost",
os.getenv("HOST_FQDN", ""), "shinastore.flatlogic.app", # Tambahin domain lo di sini
"*", # Tanda bintang ini buat bolehin semua (biar cepet fix)
] ]
CSRF_TRUSTED_ORIGINS = [ CSRF_TRUSTED_ORIGINS = [
origin for origin in [ origin for origin in [
os.getenv("HOST_FQDN", ""), os.getenv("HOST_FQDN", ""),
os.getenv("CSRF_TRUSTED_ORIGIN", "") os.getenv("CSRF_TRUSTED_ORIGIN", "")
] if origin ] if origin
] ]
CSRF_TRUSTED_ORIGINS = [ CCSRF_TRUSTED_ORIGINS = [
f"https://{host}" if not host.startswith(("http://", "https://")) else host "https://shinastore.flatlogic.app",
for host in CSRF_TRUSTED_ORIGINS "http://shinastore.flatlogic.app",
] ]
# Cookies must always be HTTPS-only; SameSite=Lax keeps CSRF working behind the proxy. # Cookies must always be HTTPS-only; SameSite=Lax keeps CSRF working behind the proxy.

Binary file not shown.

View File

@ -1,3 +1,27 @@
from django.contrib import admin from django.contrib import admin
from .models import Category, Product, Order, OrderItem, Profile
# Register your models here. @admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'description')
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ('title', 'category', 'price', 'stock', 'created_at')
list_filter = ('category', 'created_at')
search_fields = ('title', 'description')
class OrderItemInline(admin.TabularInline):
model = OrderItem
extra = 1
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ('id', 'user', 'order_date', 'total_amount', 'status')
list_filter = ('status', 'order_date')
inlines = [OrderItemInline]
@admin.register(Profile)
class ProfileAdmin(admin.ModelAdmin):
list_display = ('user', 'role')
list_filter = ('role',)

View File

Binary file not shown.

View File

View File

@ -0,0 +1,50 @@
from django.core.management.base import BaseCommand
from core.models import Category, Product
from core.pexels import fetch_first
import random
class Command(BaseCommand):
help = 'Seeds the database with initial categories and products'
def handle(self, *args, **kwargs):
categories_data = [
{'name': 'Tech', 'description': 'Latest gadgets and hardware.'},
{'name': 'Lifestyle', 'description': 'Modern living essentials.'},
{'name': 'Minimalist', 'description': 'Simple and clean aesthetics.'},
]
for cat_data in categories_data:
category, created = Category.objects.get_or_create(
name=cat_data['name'],
defaults={'description': cat_data['description']}
)
if created:
self.stdout.write(f"Created category: {category.name}")
# Sample products
products_data = [
('Minimal Desk Lamp', 'Tech', 89.99, 'minimalist desk lamp'),
('Mechanical Keyboard', 'Tech', 159.00, 'mechanical keyboard'),
('Premium Notebook', 'Minimalist', 25.50, 'aesthetic notebook'),
('Leather Wallet', 'Lifestyle', 45.00, 'leather wallet'),
('Wireless Earbuds', 'Tech', 129.00, 'modern wireless earbuds'),
('Ceramic Coffee Mug', 'Minimalist', 18.00, 'ceramic mug minimalist'),
]
for title, cat_name, price, query in products_data:
category = Category.objects.get(name=cat_name)
if not Product.objects.filter(title=title).exists():
img_data = fetch_first(query)
img_url = ""
if img_data:
img_url = img_data['local_path']
Product.objects.create(
title=title,
category=category,
price=price,
description=f"This is a premium {title.lower()} designed for quality and style.",
stock=random.randint(10, 50),
image_url=img_url
)
self.stdout.write(f"Created product: {title}")

View File

@ -0,0 +1,70 @@
# Generated by Django 5.2.7 on 2026-02-07 10:01
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)),
('description', models.TextField(blank=True)),
],
options={
'verbose_name_plural': 'Categories',
},
),
migrations.CreateModel(
name='Order',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order_date', models.DateTimeField(auto_now_add=True)),
('total_amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=10)),
('status', models.CharField(choices=[('pending', 'Pending'), ('paid', 'Paid'), ('shipped', 'Shipped'), ('cancelled', 'Cancelled')], default='pending', max_length=10)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Product',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('description', models.TextField()),
('price', models.DecimalField(decimal_places=2, max_digits=10)),
('stock', models.PositiveIntegerField(default=0)),
('image_url', models.URLField(blank=True, max_length=500)),
('created_at', models.DateTimeField(auto_now_add=True)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='core.category')),
],
),
migrations.CreateModel(
name='OrderItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.PositiveIntegerField(default=1)),
('price', models.DecimalField(decimal_places=2, max_digits=10)),
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='core.order')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.product')),
],
),
migrations.CreateModel(
name='Profile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('role', models.CharField(choices=[('admin', 'Admin'), ('customer', 'Customer')], default='customer', max_length=10)),
('avatar', models.ImageField(blank=True, null=True, upload_to='avatars/')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,3 +1,71 @@
from django.db import models from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
# Create your models here. class Profile(models.Model):
ROLE_CHOICES = (
('admin', 'Admin'),
('customer', 'Customer'),
)
user = models.OneToOneField(User, on_delete=models.CASCADE)
role = models.CharField(max_length=10, choices=ROLE_CHOICES, default='customer')
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
def __str__(self):
return f"{self.user.username} - {self.role}"
class Category(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
class Meta:
verbose_name_plural = "Categories"
def __str__(self):
return self.name
class Product(models.Model):
category = models.ForeignKey(Category, related_name='products', on_delete=models.CASCADE)
title = models.CharField(max_length=200)
description = models.TextField()
price = models.DecimalField(max_digits=10, decimal_places=2)
stock = models.PositiveIntegerField(default=0)
image_url = models.URLField(max_length=500, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
class Order(models.Model):
STATUS_CHOICES = (
('pending', 'Pending'),
('paid', 'Paid'),
('shipped', 'Shipped'),
('cancelled', 'Cancelled'),
)
user = models.ForeignKey(User, related_name='orders', on_delete=models.CASCADE)
order_date = models.DateTimeField(auto_now_add=True)
total_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='pending')
def __str__(self):
return f"Order {self.id} - {self.user.username}"
class OrderItem(models.Model):
order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField(default=1)
price = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return f"{self.quantity} x {self.product.title}"
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()

44
core/pexels.py Normal file
View File

@ -0,0 +1,44 @@
import os
from pathlib import Path
import httpx
API_KEY = os.getenv("PEXELS_KEY", "Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18")
CACHE_DIR = Path("static/images/pexels")
def _client():
return httpx.Client(
base_url="https://api.pexels.com/v1/",
headers={"Authorization": API_KEY},
timeout=15,
)
def fetch_first(query: str, orientation: str = "portrait") -> dict | None:
if not API_KEY:
return None
try:
with _client() as client:
resp = client.get(
"search",
params={"query": query, "orientation": orientation, "per_page": 1, "page": 1},
)
resp.raise_for_status()
data = resp.json()
photo = (data.get("photos") or [None])[0]
if not photo:
return None
src = photo["src"].get("large2x") or photo["src"].get("large") or photo["src"].get("original")
CACHE_DIR.mkdir(parents=True, exist_ok=True)
target = CACHE_DIR / f"{photo['id']}.jpg"
if src:
img = httpx.get(src, timeout=15)
img.raise_for_status()
target.write_bytes(img.content)
return {
"id": photo["id"],
"local_path": f"images/pexels/{photo['id']}.jpg",
"photographer": photo.get("photographer"),
"photographer_url": photo.get("photographer_url"),
}
except Exception as e:
print(f"Error fetching from Pexels: {e}")
return None

View File

@ -1,25 +1,209 @@
{% load static %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<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 %}ShinaStore | Premium E-Commerce{% endblock %}</title>
<meta name="description" content="{{ project_description }}"> <meta name="description" content="{% block meta_description %}Experience premium shopping with ShinaStore. Minimalist design, curated products.{% endblock %}">
<meta property="og:description" content="{{ project_description }}">
<meta property="twitter:description" content="{{ project_description }}"> <!-- Google Fonts -->
{% endif %} <link rel="preconnect" href="https://fonts.googleapis.com">
{% if project_image_url %} <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<meta property="og:image" content="{{ project_image_url }}"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Montserrat:wght@600;700;800&display=swap" rel="stylesheet">
<meta property="twitter:image" content="{{ project_image_url }}">
{% endif %} <!-- Bootstrap 5 CDN -->
{% load static %} <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}"> <link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
{% block head %}{% endblock %}
<style>
:root {
--primary-color: #0F172A;
--accent-color: #F43F5E;
--bg-light: #F8FAFC;
--text-main: #1E293B;
--glass-bg: rgba(255, 255, 255, 0.8);
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-light);
color: var(--text-main);
overflow-x: hidden;
}
h1, h2, h3, h4, h5, .navbar-brand {
font-family: 'Montserrat', sans-serif;
font-weight: 700;
}
.navbar {
background: var(--glass-bg);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(0,0,0,0.05);
padding: 1rem 0;
}
.navbar-brand {
font-size: 1.5rem;
letter-spacing: -1px;
color: var(--primary-color) !important;
}
.navbar-brand span {
color: var(--accent-color);
}
.btn-primary {
background-color: var(--primary-color);
border: none;
padding: 0.75rem 1.5rem;
border-radius: 8px;
font-weight: 600;
transition: all 0.3s ease;
}
.btn-primary:hover {
background-color: #1e293b;
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
}
.nav-link {
font-weight: 500;
color: var(--text-main) !important;
margin: 0 0.5rem;
}
.nav-link:hover {
color: var(--accent-color) !important;
}
.cart-badge {
position: absolute;
top: -5px;
right: -10px;
background-color: var(--accent-color);
color: white;
font-size: 0.7rem;
padding: 2px 6px;
border-radius: 50%;
}
footer {
background: var(--primary-color);
color: white;
padding: 4rem 0 2rem;
}
.footer-logo {
font-size: 1.5rem;
font-weight: 800;
margin-bottom: 1.5rem;
display: block;
}
</style>
{% block extra_css %}{% endblock %}
</head> </head>
<body> <body>
{% block content %}{% endblock %} <nav class="navbar navbar-expand-lg sticky-top">
</body> <div class="container">
<a class="navbar-brand" href="{% url 'index' %}">SHINA<span>STORE</span></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 align-items-center">
<li class="nav-item">
<a class="nav-link" href="{% url 'index' %}">Shop</a>
</li>
<li class="nav-item position-relative me-3">
<a class="nav-link" href="{% url 'cart_detail' %}">
<i class="bi bi-bag"></i>
{% if request.session.cart %}
<span class="cart-badge">{{ request.session.cart|length }}</span>
{% endif %}
</a>
</li>
{% if user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="/admin/">Dashboard</a>
</li>
<li class="nav-item">
<a class="btn btn-primary ms-lg-3" href="/admin/logout/">Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="/admin/login/?next={{ request.path }}">Login</a>
</li>
<li class="nav-item">
<a class="btn btn-primary ms-lg-3" href="/admin/">Register</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
{% if messages %}
<div class="container mt-3">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show shadow-sm" role="alert" style="border-radius: 12px;">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
</div>
{% endif %}
<main>
{% block content %}{% endblock %}
</main>
<footer>
<div class="container">
<div class="row">
<div class="col-lg-4 mb-4 mb-lg-0">
<span class="footer-logo">SHINASTORE</span>
<p class="text-secondary">Premium minimalist e-commerce experience. Curated products for the modern lifestyle.</p>
</div>
<div class="col-lg-2 col-md-4 mb-4 mb-md-0">
<h5 class="mb-4">Shop</h5>
<ul class="list-unstyled">
<li><a href="#" class="text-secondary text-decoration-none">New Arrivals</a></li>
<li><a href="#" class="text-secondary text-decoration-none">Best Sellers</a></li>
<li><a href="#" class="text-secondary text-decoration-none">Sale</a></li>
</ul>
</div>
<div class="col-lg-2 col-md-4 mb-4 mb-md-0">
<h5 class="mb-4">Company</h5>
<ul class="list-unstyled">
<li><a href="#" class="text-secondary text-decoration-none">About Us</a></li>
<li><a href="#" class="text-secondary text-decoration-none">Contact</a></li>
<li><a href="#" class="text-secondary text-decoration-none">Careers</a></li>
</ul>
</div>
<div class="col-lg-4 col-md-4">
<h5 class="mb-4">Subscribe</h5>
<div class="input-group mb-3">
<input type="text" class="form-control bg-transparent border-secondary text-white" placeholder="Email address">
<button class="btn btn-outline-light" type="button">Join</button>
</div>
</div>
</div>
<hr class="my-5 border-secondary">
<div class="text-center text-secondary">
<p>&copy; 2026 ShinaStore. All rights reserved.</p>
</div>
</div>
</footer>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{% block extra_js %}{% endblock %}
</body>
</html> </html>

View File

@ -0,0 +1,108 @@
{% extends 'base.html' %}
{% block title %}Your Cart - ShinaStore{% endblock %}
{% block content %}
<div class="container py-5">
<h1 class="display-5 fw-bold text-slate mb-5" style="font-family: 'Montserrat', sans-serif;">Your Shopping Cart</h1>
{% if cart %}
<div class="row g-5">
<div class="col-lg-8">
<div class="card border-0 shadow-sm" style="border-radius: 20px;">
<div class="table-responsive p-4">
<table class="table table-borderless align-middle">
<thead>
<tr class="text-muted small text-uppercase">
<th scope="col">Product</th>
<th scope="col">Price</th>
<th scope="col">Quantity</th>
<th scope="col">Subtotal</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for product_id, item in cart.items %}
<tr class="border-bottom">
<td class="py-4">
<div class="d-flex align-items-center">
<img src="{{ item.image_url }}" alt="{{ item.title }}" class="rounded me-3" style="width: 80px; height: 80px; object-fit: cover;">
<div>
<h6 class="fw-bold mb-0">{{ item.title }}</h6>
</div>
</div>
</td>
<td>${{ item.price }}</td>
<td>
<span class="badge bg-light text-dark px-3 py-2 border">{{ item.quantity }}</span>
</td>
<td class="fw-bold">${{ item.price }}</td>
<td class="text-end">
<a href="{% url 'remove_from_cart' product_id %}" class="btn btn-outline-danger btn-sm border-0">
<i class="bi bi-trash"></i> Remove
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card border-0 shadow-sm p-4" style="border-radius: 20px; background-color: #F8FAFC;">
<h4 class="fw-bold mb-4">Order Summary</h4>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">Subtotal</span>
<span>${{ total|stringformat:".2f" }}</span>
</div>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">Shipping</span>
<span class="text-success">Free</span>
</div>
<hr>
<div class="d-flex justify-content-between mb-4">
<span class="fw-bold">Total</span>
<span class="h4 fw-bold text-rose">${{ total|stringformat:".2f" }}</span>
</div>
{% if user.is_authenticated %}
<a href="{% url 'checkout' %}" class="btn btn-dark btn-lg w-100 py-3 fw-bold" style="border-radius: 12px;">
Proceed to Checkout
</a>
{% else %}
<div class="alert alert-info small" style="border-radius: 12px;">
Please <a href="/admin/login/?next={{ request.path }}" class="fw-bold">login</a> to place an order.
</div>
<button class="btn btn-dark btn-lg w-100 py-3 fw-bold" disabled style="border-radius: 12px;">
Proceed to Checkout
</button>
{% endif %}
<div class="mt-4 text-center">
<a href="{% url 'index' %}" class="text-decoration-none text-muted small">
<i class="bi bi-arrow-left"></i> Continue Shopping
</a>
</div>
</div>
</div>
</div>
{% else %}
<div class="text-center py-5">
<div class="mb-4">
<i class="bi bi-cart-x display-1 text-muted"></i>
</div>
<h3>Your cart is empty</h3>
<p class="text-muted">Looks like you haven't added anything to your cart yet.</p>
<a href="{% url 'index' %}" class="btn btn-rose btn-lg px-5 py-3 mt-3 text-white fw-bold" style="background-color: #F43F5E; border-radius: 12px;">
Start Shopping
</a>
</div>
{% endif %}
</div>
<style>
.text-rose { color: #F43F5E; }
.btn-rose:hover { background-color: #E11D48 !important; }
</style>
{% endblock %}

View File

@ -0,0 +1,52 @@
{% extends 'base.html' %}
{% block title %}Order Confirmed - ShinaStore{% endblock %}
{% block content %}
<div class="container py-5 text-center">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="mb-4">
<i class="bi bi-check-circle-fill display-1 text-success"></i>
</div>
<h1 class="display-4 fw-bold text-slate mb-3" style="font-family: 'Montserrat', sans-serif;">Thank You!</h1>
<p class="h4 text-muted mb-5">Your order #{{ order.id }} has been placed successfully.</p>
<div class="card border-0 shadow-sm p-4 mb-5" style="border-radius: 20px; text-align: left;">
<h5 class="fw-bold mb-4">Order Summary</h5>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">Order Date:</span>
<span>{{ order.order_date|date:"F d, Y H:i" }}</span>
</div>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">Order Total:</span>
<span class="fw-bold">${{ order.total_amount }}</span>
</div>
<div class="d-flex justify-content-between mb-4">
<span class="text-muted">Status:</span>
<span class="badge bg-success">{{ order.get_status_display }}</span>
</div>
<hr>
<h6 class="fw-bold mb-3">Items Ordered:</h6>
{% for item in order.items.all %}
<div class="d-flex justify-content-between mb-2 small">
<span>{{ item.quantity }} x {{ item.product.title }}</span>
<span>${{ item.price }}</span>
</div>
{% endfor %}
</div>
<div class="d-grid gap-3 d-sm-flex justify-content-sm-center">
<a href="{% url 'index' %}" class="btn btn-dark btn-lg px-4 py-3 fw-bold" style="border-radius: 12px;">
Back to Store
</a>
<button onclick="window.print()" class="btn btn-outline-dark btn-lg px-4 py-3 fw-bold" style="border-radius: 12px;">
Print Receipt
</button>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,145 +1,120 @@
{% extends "base.html" %} {% extends 'base.html' %}
{% load static %}
{% block title %}{{ project_name }}{% endblock %} {% block content %}
<!-- Hero Section -->
<section class="hero-section py-5 mb-5" style="background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); position: relative; overflow: hidden;">
<div class="container py-5">
<div class="row align-items-center">
<div class="col-lg-6 mb-5 mb-lg-0">
<h1 class="display-3 mb-4" style="line-height: 1.1;">Define Your <span style="color: var(--accent-color);">Minimalist</span> Style.</h1>
<p class="lead mb-5 text-secondary">Discover our curated collection of premium gadgets and lifestyle essentials designed for the modern home.</p>
<div class="d-flex gap-3">
<a href="#products" class="btn btn-primary btn-lg px-4">Shop Collection</a>
<a href="#" class="btn btn-outline-dark btn-lg px-4">Learn More</a>
</div>
</div>
<div class="col-lg-6">
<div class="position-relative">
<div class="hero-shape" style="position: absolute; width: 400px; height: 400px; background: rgba(244, 63, 94, 0.1); border-radius: 50%; filter: blur(50px); top: -50px; right: -50px; z-index: 0;"></div>
<img src="https://images.pexels.com/photos/129731/pexels-photo-129731.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2" alt="Hero Image" class="img-fluid rounded-4 shadow-lg position-relative" style="z-index: 1;">
</div>
</div>
</div>
</div>
</section>
<!-- Filter & Search Section -->
<section class="mb-5" id="products">
<div class="container">
<div class="row align-items-center mb-4">
<div class="col-md-6 mb-3 mb-md-0">
<h2 class="mb-0">Featured Products</h2>
</div>
<div class="col-md-6">
<form action="{% url 'index' %}" method="GET" class="d-flex gap-2">
<input type="text" name="q" class="form-control rounded-pill px-4" placeholder="Search products..." value="{{ query|default:'' }}">
{% if selected_category %}
<input type="hidden" name="category" value="{{ selected_category }}">
{% endif %}
<button type="submit" class="btn btn-primary rounded-pill px-4">Search</button>
</form>
</div>
</div>
<!-- Categories -->
<div class="d-flex gap-2 overflow-auto pb-3 mb-5" style="scrollbar-width: none;">
<a href="{% url 'index' %}" class="btn {% if not selected_category %}btn-dark{% else %}btn-outline-dark{% endif %} rounded-pill px-4">All</a>
{% for category in categories %}
<a href="{% url 'index' %}?category={{ category.id }}{% if query %}&q={{ query }}{% endif %}"
class="btn {% if selected_category == category.id %}btn-dark{% else %}btn-outline-dark{% endif %} rounded-pill px-4">
{{ category.name }}
</a>
{% endfor %}
</div>
<!-- Product Grid -->
<div class="row g-4">
{% for product in products %}
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 border-0 shadow-sm rounded-4 overflow-hidden product-card">
<a href="{% url 'product_detail' product.pk %}" class="text-decoration-none">
<div class="position-relative overflow-hidden" style="height: 250px;">
{% if product.image_url %}
<img src="{% static product.image_url %}" class="card-img-top h-100 w-100 object-fit-cover transition-scale" alt="{{ product.title }}">
{% else %}
<div class="h-100 w-100 bg-light d-flex align-items-center justify-content-center">
<span class="text-muted">No image</span>
</div>
{% endif %}
<div class="position-absolute top-0 end-0 p-3">
<span class="badge bg-white text-dark shadow-sm rounded-pill">${{ product.price }}</span>
</div>
</div>
</a>
<div class="card-body p-4 d-flex flex-column">
<small class="text-uppercase text-secondary fw-bold" style="font-size: 0.7rem; letter-spacing: 1px;">{{ product.category.name }}</small>
<h5 class="card-title mt-1 mb-2">
<a href="{% url 'product_detail' product.pk %}" class="text-decoration-none text-dark">{{ product.title }}</a>
</h5>
<p class="card-text text-secondary small mb-4 text-truncate-2">{{ product.description }}</p>
<div class="mt-auto">
<a href="{% url 'add_to_cart' product.pk %}" class="btn btn-outline-dark w-100 rounded-pill">Add to Cart</a>
</div>
</div>
</div>
</div>
{% empty %}
<div class="col-12 text-center py-5">
<p class="text-muted">No products found matching your criteria.</p>
</div>
{% endfor %}
</div>
</div>
</section>
{% 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> <style>
:root { .product-card {
--bg-color-start: #6a11cb; transition: transform 0.3s ease, box-shadow 0.3s ease;
--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);
} }
.product-card:hover {
* { transform: translateY(-10px);
box-sizing: border-box; box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04) !important;
} }
.transition-scale {
body { transition: transform 0.5s ease;
margin: 0; }
font-family: 'Inter', sans-serif; .product-card:hover .transition-scale {
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end)); transform: scale(1.1);
color: var(--text-color); }
display: flex; .text-truncate-2 {
justify-content: center; display: -webkit-box;
align-items: center; -webkit-line-clamp: 2;
min-height: 100vh; -webkit-box-orient: vertical;
text-align: center;
overflow: hidden; overflow: hidden;
position: relative;
} }
.object-fit-cover {
body::before { object-fit: cover;
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> </style>
{% endblock %} {% endblock %}
{% block content %}
<main>
<div class="card">
<h1>Analyzing your requirements and generating your app…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
</div>
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
<p class="runtime">
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
</p>
</div>
</main>
<footer>
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
</footer>
{% endblock %}

View File

@ -0,0 +1,104 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}{{ product.title }} - ShinaStore{% endblock %}
{% block content %}
<div class="container py-5">
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'index' %}" class="text-decoration-none text-muted">Home</a></li>
<li class="breadcrumb-item"><a href="{% url 'index' %}?category={{ product.category.id }}" class="text-decoration-none text-muted">{{ product.category.name }}</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ product.title }}</li>
</ol>
</nav>
<div class="row g-5">
<div class="col-md-6">
<div class="card border-0 shadow-sm overflow-hidden" style="border-radius: 24px;">
<img src="{{ product.image_url }}" class="img-fluid w-100" alt="{{ product.title }}" style="min-height: 500px; object-fit: cover;">
</div>
</div>
<div class="col-md-6">
<div class="ps-md-4">
<span class="badge bg-rose mb-3 px-3 py-2" style="background-color: #F43F5E;">{{ product.category.name }}</span>
<h1 class="display-5 fw-bold text-slate mb-3" style="font-family: 'Montserrat', sans-serif;">{{ product.title }}</h1>
<p class="h3 text-rose fw-bold mb-4" style="color: #F43F5E;">${{ product.price }}</p>
<div class="mb-4">
<h5 class="fw-bold mb-3">Description</h5>
<p class="text-muted leading-relaxed">
{{ product.description }}
</p>
</div>
<div class="mb-4">
<p class="text-muted">Stock availability:
{% if product.stock > 0 %}
<span class="text-success fw-bold">In Stock ({{ product.stock }})</span>
{% else %}
<span class="text-danger fw-bold">Out of Stock</span>
{% endif %}
</p>
</div>
<div class="d-grid gap-2">
{% if product.stock > 0 %}
<a href="{% url 'add_to_cart' product.pk %}" class="btn btn-lg py-3 fw-bold shadow-sm" style="background-color: #0F172A; color: white; border-radius: 12px; transition: transform 0.2s;">
Add to Cart
</a>
{% else %}
<button class="btn btn-lg btn-secondary py-3 fw-bold" disabled style="border-radius: 12px;">
Out of Stock
</button>
{% endif %}
</div>
<div class="mt-5 p-4 bg-light" style="border-radius: 16px; border-left: 4px solid #F43F5E;">
<div class="d-flex align-items-center">
<div class="me-3">
<i class="bi bi-shield-check h3 text-rose"></i>
</div>
<div>
<h6 class="mb-1 fw-bold">Secure Checkout</h6>
<p class="mb-0 small text-muted">100% Secure payment with 256-bit SSL encryption.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Related Products -->
{% if related_products %}
<div class="mt-5 pt-5">
<h3 class="fw-bold mb-4" style="font-family: 'Montserrat', sans-serif;">You might also like</h3>
<div class="row g-4">
{% for rel in related_products %}
<div class="col-md-3">
<div class="card h-100 border-0 shadow-sm product-card" style="border-radius: 20px; transition: all 0.3s ease;">
<a href="{% url 'product_detail' rel.pk %}" class="text-decoration-none">
<img src="{{ rel.image_url }}" class="card-img-top" alt="{{ rel.title }}" style="height: 250px; object-fit: cover; border-top-left-radius: 20px; border-top-right-radius: 20px;">
<div class="card-body p-3">
<h6 class="text-slate fw-bold mb-1">{{ rel.title }}</h6>
<p class="text-rose fw-bold mb-0">${{ rel.price }}</p>
</div>
</a>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
<style>
.product-card:hover {
transform: translateY(-10px);
box-shadow: 0 10px 25px rgba(0,0,0,0.1) !important;
}
.bg-rose { background-color: #F43F5E; }
.text-rose { color: #F43F5E; }
.text-slate { color: #0F172A; }
</style>
{% endblock %}

View File

@ -1,7 +1,11 @@
from django.urls import path from django.urls import path
from . import views
from .views import home
urlpatterns = [ urlpatterns = [
path("", home, name="home"), path("", views.index, name="index"),
path("product/<int:pk>/", views.product_detail, name="product_detail"),
path("cart/", views.cart_detail, name="cart_detail"),
path("cart/add/<int:pk>/", views.add_to_cart, name="add_to_cart"),
path("cart/remove/<int:pk>/", views.remove_from_cart, name="remove_from_cart"),
path("checkout/", views.checkout, name="checkout"),
] ]

View File

@ -1,25 +1,97 @@
import os from django.shortcuts import render, get_object_or_404, redirect
import platform from django.contrib.auth.decorators import login_required
from .models import Product, Category, Order, OrderItem
from django.contrib import messages
from django import get_version as django_version def index(request):
from django.shortcuts import render query = request.GET.get('q')
from django.utils import timezone category_id = request.GET.get('category')
products = Product.objects.all().order_by('-created_at')
categories = Category.objects.all()
def home(request): if query:
"""Render the landing screen with loader and environment details.""" products = products.filter(title__icontains=query) | products.filter(description__icontains=query)
host_name = request.get_host().lower()
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" if category_id:
now = timezone.now() products = products.filter(category_id=category_id)
context = { context = {
"project_name": "New Style", 'products': products,
"agent_brand": agent_brand, 'categories': categories,
"django_version": django_version(), 'selected_category': int(category_id) if category_id and category_id.isdigit() else None,
"python_version": platform.python_version(), 'query': query
"current_time": 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, pk):
product = get_object_or_404(Product, pk=pk)
related_products = Product.objects.filter(category=product.category).exclude(pk=pk)[:4]
return render(request, 'core/product_detail.html', {
'product': product,
'related_products': related_products
})
def add_to_cart(request, pk):
product = get_object_or_404(Product, pk=pk)
cart = request.session.get('cart', {})
product_id = str(pk)
if product_id in cart:
cart[product_id]['quantity'] += 1
else:
cart[product_id] = {
'title': product.title,
'price': str(product.price),
'quantity': 1,
'image_url': product.image_url
}
request.session['cart'] = cart
messages.success(request, f"{product.title} added to cart.")
return redirect('cart_detail')
def cart_detail(request):
cart = request.session.get('cart', {})
total = sum(float(item['price']) * item['quantity'] for item in cart.values())
return render(request, 'core/cart.html', {'cart': cart, 'total': total})
def remove_from_cart(request, pk):
cart = request.session.get('cart', {})
product_id = str(pk)
if product_id in cart:
del cart[product_id]
request.session['cart'] = cart
messages.info(request, "Item removed from cart.")
return redirect('cart_detail')
@login_required
def checkout(request):
cart = request.session.get('cart', {})
if not cart:
messages.warning(request, "Your cart is empty.")
return redirect('index')
total = sum(float(item['price']) * item['quantity'] for item in cart.values())
# Create Order
order = Order.objects.create(user=request.user, total_amount=total)
# Create OrderItems
for product_id, item in cart.items():
product = get_object_or_404(Product, pk=product_id)
OrderItem.objects.create(
order=order,
product=product,
quantity=item['quantity'],
price=item['price']
)
# Update stock
if product.stock >= item['quantity']:
product.stock -= item['quantity']
product.save()
# Clear cart
request.session['cart'] = {}
return render(request, 'core/checkout_success.html', {'order': order})

View File

@ -1,3 +1,36 @@
anyio==4.12.1
asgiref==3.10.0
certifi==2022.9.24
chardet==5.1.0
charset-normalizer==3.0.1
dbus-python==1.3.2
distro-info==1.5+deb12u1
Django==5.2.7 Django==5.2.7
h11==0.16.0
httpcore==1.0.9
httplib2==0.20.4
httpx==0.28.1
idna==3.3
markdown-it-py==2.1.0
mdurl==0.1.2
mysqlclient==2.2.7 mysqlclient==2.2.7
netifaces==0.11.0
pillow==12.1.0
pycurl==7.45.2
Pygments==2.14.0
PyGObject==3.42.2
pyparsing==3.0.9
PySimpleSOAP==1.16.2
python-apt==2.6.0
python-debian==0.1.49
python-debianbts==4.0.1
python-dotenv==1.1.1 python-dotenv==1.1.1
PyYAML==6.0
reportbug==12.0.0
requests==2.28.1
rich==13.3.1
six==1.16.0
sqlparse==0.5.3
typing_extensions==4.15.0
unattended-upgrades==0.1
urllib3==1.26.12

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB