Skip to content

Commit

Permalink
feat: create product with ajax
Browse files Browse the repository at this point in the history
  • Loading branch information
fazirta committed Oct 8, 2024
1 parent ed28f37 commit 5c452f6
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 52 deletions.
97 changes: 97 additions & 0 deletions main/static/profile/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
async function getProducts() {
return fetch("/json").then((res) => res.json())
}

async function refreshProducts() {
document.getElementById("product_cards").innerHTML = "";
document.getElementById("product_cards").className = "";
const products = await getProducts();
let htmlString = "";
let classNameString = "";

if (products.length === 0) {
classNameString = "mt-4 flex justify-center items-center";
htmlString = `
<img src="/static/empty-box.png" alt="empty box" class="max-w-36 md:max-w-64">
<div class="max-w-64">
<h1 class="text-lg md:text-xl font-semibold">No Products Found</h1>
<h2 class="text-neutral-500 mt-2">
It looks like you haven't registered any products yet. Start adding some to see them here!
</h2>
</div>
`;
} else {
classNameString = "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3 md:gap-5 mt-4"
products.forEach((item) => {
htmlString += `
<div class="mx-auto rounded-xl shadow hover:shadow-lg max-w-52 transition duration-300">
<a href="/json/${item.pk}">
<img src="/media/${item.fields.image}" class="w-full rounded-t-xl">
<div class="px-3 py-2 flex flex-col gap-2">
<h1 class="font-semibold">${item.fields.name}</h1>
<h2 class="font-bold text-sm md:text-lg">Rp${item.fields.price}</h2>
</div>
</a>
<div class="px-3 py-2 flex flex-col gap-2">
<a href="/edit/${item.pk}"
class="w-full py-2 bg-blue-500 rounded-xl font-semibold text-white text-center hover:shadow hover:bg-blue-600 transition duration-300">
<i class="fa-solid fa-pencil mr-2"></i>
Edit
</a>
<a href="/delete/${item.pk}"
class="w-full py-2 bg-red-500 rounded-xl font-semibold text-white text-center hover:shadow hover:bg-red-600 transition duration-300">
<i class="fa-solid fa-trash mr-2"></i>
Delete
</a>
</div>
</div>
`;
});
}
document.getElementById("product_cards").className = classNameString;
document.getElementById("product_cards").innerHTML = htmlString;
}
refreshProducts();

const modal = document.getElementById('modal');
const modalContent = document.getElementById('modalContent');

function showModal() {
const modal = document.getElementById('modal');
const modalContent = document.getElementById('modalContent');

modal.classList.remove('hidden');
setTimeout(() => {
modalContent.classList.remove('opacity-0', 'scale-95');
modalContent.classList.add('opacity-100', 'scale-100');
}, 50);
}

function hideModal() {
const modal = document.getElementById('modal');
const modalContent = document.getElementById('modalContent');

modalContent.classList.remove('opacity-100', 'scale-100');
modalContent.classList.add('opacity-0', 'scale-95');

setTimeout(() => {
modal.classList.add('hidden');
}, 150);
}

document.getElementById("cancelButton").addEventListener("click", hideModal);
document.getElementById("closeModalButton").addEventListener("click", hideModal);

function addProduct() {
fetch("/create-ajax", {
method: "POST",
body: new FormData(document.querySelector('#productForm')),
}).then(response => refreshProducts())

document.getElementById("productForm").reset();
hideModal();

return false;
}

document.getElementById("submitProductForm").onclick = addProduct
45 changes: 17 additions & 28 deletions main/templates/profile/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,26 @@
class="fa-solid fa-user text-3xl sm:text-5xl bg-gradient-to-r from-blue-900 to-red-900 inline-block text-transparent bg-clip-text"></i>
</div>
</div>
<div class="flex flex-col">
<div class="flex flex-col md:flex-1">
<h1 class="text-2xl font-semibold mt-3.5">{{ request.user.username }}</h1>
<h2 class="text-xs font-semibold text-neutral-500 text-center mt-1">
Last login: {{ request.user.last_login }}
<h2 class="text-xs font-semibold text-neutral-500 mt-1"> Last login: {{ request.user.last_login }}
</h2>
<a href="{% url 'main:signout' %}"
class="mt-4 w-full py-2 bg-red-500 rounded-xl font-semibold text-white text-center hover:shadow hover:bg-red-600 transition duration-300">
<i class="fa-solid fa-arrow-right-from-bracket mr-2"></i>
Sign out
</a>
</div>
</div>
<div class="mt-8 py-2 rounded-2xl">
<h1 class="font-bold text-xl md:text-2xl">Your Products</h1>
{% if not products %}
<div class="mt-4 flex justify-center items-center">
<img src="{% static 'empty-box.png' %}" alt="empty box" class="max-w-36 md:max-w-64">
<div class="max-w-64">
<h1 class="text-lg md:text-xl font-semibold">No Products Found</h1>
<h2 class="text-neutral-500 mt-2">
It looks like you haven't registered any products yet. Start adding some to see them here!
</h2>
<div class="mt-4 flex flex-wrap gap-2">
<a href="{% url 'main:signout' %}"
class="max-w-48 w-full py-2 bg-red-500 rounded-xl font-semibold text-white text-center hover:shadow hover:bg-red-600 transition duration-300">
<i class="fa-solid fa-arrow-right-from-bracket mr-2"></i> Sign out </a>
<button data-modal-target="modal" data-modal-toggle="modal"
class="max-w-48 w-full py-2 bg-emerald-500 rounded-xl font-semibold text-white text-center hover:shadow hover:bg-emerald-600 transition duration-300"
onclick="showModal();">
<i class="fa-solid fa-plus mr-2"></i> Create Product </button>
</div>
</div>
{% else %}
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3 md:gap-5 mt-4">
{% for product in products %}
{% include 'profile/product.html' %}
{% endfor %}
</div>
{% endif %}
</div> {% include 'profile/modal.html' %} <div class="mt-8 py-2 rounded-2xl">
<h1 class="font-bold text-xl md:text-2xl">Your Products</h1>
<div id="product_cards"></div>
</div>
</main>
{% endblock content %}
{% endblock content %}
{% block script %}
<script type="text/javascript" src="{% static 'profile/script.js' %}"></script>
{% endblock script %}
63 changes: 63 additions & 0 deletions main/templates/profile/modal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<div id="modal" tabindex="-1" aria-hidden="true"
class="hidden fixed inset-0 z-50 w-full flex items-center justify-center bg-gray-800 bg-opacity-50 overflow-x-hidden overflow-y-auto transition-opacity duration-300 ease-out">
<div id="modalContent"
class="relative bg-white rounded-lg shadow-lg w-5/6 sm:w-3/4 md:w-1/2 lg:w-1/3 mx-4 sm:mx-0 transform scale-95 opacity-0 transition-transform transition-opacity duration-300 ease-out">
<!-- Modal header -->
<div class="flex items-center justify-between p-4 border-b rounded-t">
<h3 class="text-xl font-semibold text-gray-900">Create Product</h3>
<button type="button"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 transition rounded-lg text-sm p-1.5 ml-auto inline-flex items-center"
id="closeModalButton">
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"></path>
</svg>
<span class="sr-only">Close modal</span>
</button>
</div>
<!-- Modal body -->
<div class="px-6 py-4 space-y-6 form-style">
<form method="POST" enctype="multipart/form-data" id="productForm"
class="w-full max-w-md md:max-w-2xl mx-auto p-2 md:p-8 rounded-2xl flex flex-col gap-5">
<div class="flex flex-col gap-2">
<label class="text-neutral-600 font-bold">Name</label>
<input type="text" name="name" maxlength="255" required="" aria-invalid="true"
placeholder="Rexus Bluetooth Gamepad Gladius GX300"
class="flex h-10 w-full rounded-[12px] border-2 border-slate-500/50 bg-transparent px-5 py-3 text-sm focus-visible:outline-none focus-visible:border-slate-500">
</div>
<div class="flex flex-col gap-2">
<label class="text-neutral-600 font-bold">Price</label>
<input type="number" name="price" required="" aria-invalid="true" placeholder="409000"
class="flex h-10 w-full rounded-[12px] border-2 border-slate-500/50 bg-transparent px-5 py-3 text-sm focus-visible:outline-none focus-visible:border-slate-500">
</div>
<div class="flex flex-col gap-2">
<label class="text-neutral-600 font-bold">Description</label>
<textarea name="description" cols="40" rows="3" required="" aria-invalid="true"
placeholder="This ergonomic gamepad features a textured grip for non-slip play, a 3.5mm TRRS port for microphones and headsets, a touchpad, hall magnetic trigger sensors, and a six-axis sensor. It has a 600 mAh battery rechargeable via a Type-C to USB cable."
class="flex w-full rounded-[12px] border-2 border-slate-500/50 bg-transparent px-5 py-3 text-sm focus-visible:outline-none focus-visible:border-slate-500"></textarea>
</div>
<div class="flex flex-col gap-2">
<label class="text-neutral-600 font-bold">Stock</label>
<input type="number" name="stock" min="0" required="" aria-invalid="true" placeholder="887"
class="flex h-10 w-full rounded-[12px] border-2 border-slate-500/50 bg-transparent px-5 py-3 text-sm focus-visible:outline-none focus-visible:border-slate-500">
</div>
<div class="flex flex-col gap-2">
<label class="text-neutral-600 font-bold">Image</label>
<input type="file" name="image" accept="image/*" required="" aria-invalid="true" placeholder=""
class="flex h-10 w-full rounded-[12px] border-2 border-slate-500/50 bg-transparent px-5 py-3 text-sm focus-visible:outline-none focus-visible:border-slate-500">
</div>
</form>
</div>
<!-- Modal footer -->
<div
class="flex flex-col space-y-2 md:flex-row md:space-y-0 md:space-x-2 p-6 border-t border-gray-200 rounded-b justify-center md:justify-end">
<button type="button"
class="bg-red-500 hover:bg-red-600 hover:shadow transition duration-300 text-white font-bold py-2 px-4 rounded-lg"
id="cancelButton">Cancel</button>
<button type="submit" id="submitProductForm" form="productForm"
class="bg-blue-700 hover:bg-blue-600 hover:shadow transition duration-300 text-white font-bold py-2 px-4 rounded-lg">Create</button>
</div>
</div>
</div>
22 changes: 0 additions & 22 deletions main/templates/profile/product.html

This file was deleted.

2 changes: 2 additions & 0 deletions main/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from main.views import (
landing,
create,
create_ajax,
edit,
delete,
show_xml,
Expand All @@ -19,6 +20,7 @@
urlpatterns = [
path("", landing, name="landing"),
path("create", create, name="create"),
path("create-ajax", create_ajax, name="create_ajax"),
path("edit/<uuid:id>", edit, name="edit"),
path("delete/<uuid:id>", delete, name="delete"),
path("xml/", show_xml, name="show_xml"),
Expand Down
30 changes: 28 additions & 2 deletions main/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth import login, logout
from django.shortcuts import render, redirect
from django.http import HttpResponseRedirect
from django.utils.html import strip_tags
from django.http import HttpResponse
from django.contrib import messages
from django.core import serializers
Expand All @@ -16,6 +19,29 @@ def landing(request):
return render(request, "landing/index.html", {"products": Product.objects.all()})


@csrf_exempt
@require_POST
def create_ajax(request):
name = strip_tags(request.POST.get("name"))
price = request.POST.get("price")
description = strip_tags(request.POST.get("description"))
stock = request.POST.get("stock")
image = request.FILES.get("image")
user = request.user

new_product = Product(
name=name,
price=price,
description=description,
stock=stock,
image=image,
user=user,
)
new_product.save()

return HttpResponse(b"CREATED", status=201)


@login_required(login_url="/signin")
def create(request):
form = ProductForm(request.POST or None, request.FILES)
Expand Down Expand Up @@ -46,14 +72,14 @@ def delete(request, id):


def show_xml(request):
data = Product.objects.all()
data = Product.objects.filter(user=request.user)
return HttpResponse(
serializers.serialize("xml", data), content_type="application/xml"
)


def show_json(request):
data = Product.objects.all()
data = Product.objects.filter(user=request.user)
return HttpResponse(
serializers.serialize("json", data), content_type="application/json"
)
Expand Down

0 comments on commit 5c452f6

Please sign in to comment.