Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -31,5 +31,8 @@ jobs:
|
||||
script: |
|
||||
cd /root/hshop/
|
||||
docker compose down
|
||||
docker compose build
|
||||
docker compose build --no-cache frontend
|
||||
docker compose build backend
|
||||
docker compose build db-backup
|
||||
docker compose build db
|
||||
docker compose up -d
|
||||
+2
-2
@@ -21,8 +21,8 @@ API_DOMAIN = 'api.heymlz.com'
|
||||
SITE_TITLE = 'فروشگاه هی ملز'
|
||||
SITE_HEADER = 'فروشگاه هی ملز'
|
||||
# jwt token configs
|
||||
ACCESS_TOKEN_LIFETIME = 5000
|
||||
REFRESH_TOKEN_LIFETIME = 5000
|
||||
ACCESS_TOKEN_LIFETIME = 1
|
||||
REFRESH_TOKEN_LIFETIME = 5
|
||||
|
||||
SMS_API_KEY = ''
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
del self.fields['password']
|
||||
|
||||
|
||||
class ProfileSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
|
||||
@@ -70,7 +70,8 @@ Code: {otp}"""
|
||||
except User.DoesNotExist:
|
||||
return Response({'detail': 'user not found'}, status=status.HTTP_404_NOT_FOUND)
|
||||
except Exception as e:
|
||||
return Response({'detail': f'An error occurred: {e}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
return Response({'detail': f'error: {e} مشتی فعلا برو تو غمت نباشه تا بعدا یه کاریش بکنم', 'otp_code': otp}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
# return Response({'detail': f'An error occurred: {e}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
class CustomTokenObtainPairView(TokenObtainPairView):
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.2 on 2025-03-06 17:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('order', '0009_alter_ordermodel_created_at'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='orderitemmodel',
|
||||
name='quantity',
|
||||
field=models.PositiveSmallIntegerField(verbose_name='تعداد'),
|
||||
),
|
||||
]
|
||||
@@ -46,8 +46,11 @@ class OrderModel(models.Model):
|
||||
verbose_name = 'سفارش'
|
||||
verbose_name_plural = 'سفارشات'
|
||||
|
||||
|
||||
# def total_without_tax(self):
|
||||
# return sum(item.total() for item in self.items.all())
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
try:
|
||||
push_object = PushSubscription.objects.get(user=self.user)
|
||||
@@ -59,6 +62,7 @@ class OrderModel(models.Model):
|
||||
print('didnt send')
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
def discount(self):
|
||||
pass
|
||||
# total_with_item_discount = sum(item.total_with_discount() for item in self.items.all())
|
||||
@@ -73,12 +77,14 @@ class OrderModel(models.Model):
|
||||
def tax(self):
|
||||
return self.total_without_tax() * 0.2
|
||||
|
||||
|
||||
def total(self):
|
||||
pass
|
||||
# return self.total_with_discount() + self.tax()
|
||||
|
||||
def remove_order_item(self, item_pk, quantity):
|
||||
pass
|
||||
|
||||
def add_order_item(self, item_pk, quantity):
|
||||
status = ''
|
||||
return status
|
||||
@@ -90,7 +96,7 @@ class OrderModel(models.Model):
|
||||
|
||||
class OrderItemModel(models.Model):
|
||||
order = models.ForeignKey(OrderModel, on_delete=models.CASCADE, related_name='items', verbose_name='سفارش')
|
||||
quantity = models.SmallIntegerField(verbose_name="تعداد")
|
||||
quantity = models.PositiveSmallIntegerField(verbose_name="تعداد")
|
||||
product = models.ForeignKey(ProductVariant, on_delete=models.PROTECT, verbose_name="محصول")
|
||||
class Meta:
|
||||
verbose_name = 'ایتم سبد خرید'
|
||||
|
||||
@@ -21,7 +21,14 @@ class OrderSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = OrderModel
|
||||
fields = ['address', 'created_at', 'is_paid', 'status', 'discount_code', "images", "count", "id"]
|
||||
|
||||
def get_count(self, obj):
|
||||
return obj.items.all().count()
|
||||
|
||||
def get_images(self, obj):
|
||||
return ["a" , "b" , "c"]
|
||||
image_list = [
|
||||
self.context.get('request').build_absolute_uri(image.image.url)
|
||||
if (image := item.product.images.all().first()) else None
|
||||
for item in obj.items.all()[:3]
|
||||
]
|
||||
return filter(lambda x: x is not None, image_list)
|
||||
@@ -31,6 +31,7 @@ class CartItemViews(APIView):
|
||||
product_variant = get_object_or_404(ProductVariant, pk=pk)
|
||||
response = 'محصول با موفقیت به سبد خرید اضافه شد'
|
||||
quantity = request.data.get('quantity', 1)
|
||||
quantity = max(quantity, 0)
|
||||
if product_variant.in_stock < quantity:
|
||||
quantity = product_variant.in_stock
|
||||
response = 'تعداد درخواستی بیشتر از موجودی محصول میباشد'
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Ticket, Message, Attachment
|
||||
from django.utils.timezone import localtime
|
||||
from account.serializers import ProfileSerializer
|
||||
from order.serializers import OrderSerializer
|
||||
from order.serializers import OrderModel
|
||||
|
||||
|
||||
@@ -23,3 +23,8 @@ node_modules
|
||||
.env.*
|
||||
!.env.example
|
||||
/test-results/.last-run.json
|
||||
|
||||
# Lock files
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
|
||||
+12
-5
@@ -1,23 +1,31 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
// import
|
||||
|
||||
import { VueQueryDevtools } from "@tanstack/vue-query-devtools";
|
||||
|
||||
// state
|
||||
|
||||
useState('showLoadingOverlay', () => true);
|
||||
const { $updateAvailable: updateAvailable, $handleUpdate: handleUpdate } =
|
||||
useNuxtApp();
|
||||
|
||||
const closeModal = () => {
|
||||
updateAvailable.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<LoadingIndicator />
|
||||
|
||||
<NuxtRouteAnnouncer />
|
||||
<NuxtPwaManifest />
|
||||
|
||||
<UpdatePwaModal
|
||||
:isShow="updateAvailable"
|
||||
@update="handleUpdate"
|
||||
@close="closeModal"
|
||||
/>
|
||||
|
||||
<NuxtLayout>
|
||||
|
||||
<ToastProvider>
|
||||
<NuxtPage />
|
||||
|
||||
@@ -29,6 +37,5 @@ useState('showLoadingOverlay', () => true);
|
||||
</NuxtLayout>
|
||||
|
||||
<VueQueryDevtools dir="ltr" buttonPosition="bottom-left" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<script setup lang="ts">
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
isShow: boolean;
|
||||
};
|
||||
|
||||
type Emits = {
|
||||
update: [value: any];
|
||||
"update:isShow": [value: boolean];
|
||||
};
|
||||
|
||||
// props
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const { isShow } = toRefs(props);
|
||||
|
||||
// emits
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
// computed
|
||||
|
||||
const visible = computed({
|
||||
get: () => isShow.value ?? false,
|
||||
set: (value: boolean) => emit("update:isShow", value),
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
v-model="visible"
|
||||
title="ورژن جدید"
|
||||
contectClass="!w-[90vw] lg:!w-[35vw]"
|
||||
@close="visible = false"
|
||||
>
|
||||
<template #content>
|
||||
<div class="w-full flex flex-col text-start gap-3 py-5" dir="rtl">
|
||||
<p>
|
||||
نسخه جدید و بهبود یافته اپلیکیشن با ویژگیهای جذاب منتظر
|
||||
شماست
|
||||
</p>
|
||||
<p>برای تجربه بهتر، لطفا نسخه فعلی را بروزرسانی کنید</p>
|
||||
<p>
|
||||
پس از کلیک بر روی گزینه دریافت نسخه جدید، برنامه به صورت
|
||||
خودکار بروز میشود
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="py-6 border-t border-slate-200 flex gap-3">
|
||||
<Button
|
||||
@click="emit('update', null)"
|
||||
class="rounded-full px-10"
|
||||
>
|
||||
<span>دریافت ورژن جدید</span>
|
||||
</Button>
|
||||
<DialogClose aria-label="Close">
|
||||
<Button variant="outlined" class="rounded-full px-10">
|
||||
انصراف
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
FROM node:20-alpine as build-stage
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
RUN npm install --force
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
|
||||
+34
-1
@@ -14,6 +14,9 @@ export default defineNuxtConfig({
|
||||
},
|
||||
|
||||
app: {
|
||||
head: {
|
||||
title: "فروشگاه هی ملز",
|
||||
},
|
||||
pageTransition: {
|
||||
enterActiveClass:
|
||||
"animate__animated animate__fadeIn animate__faster",
|
||||
@@ -69,9 +72,39 @@ export default defineNuxtConfig({
|
||||
"@nuxt/icon",
|
||||
"reka-ui/nuxt",
|
||||
"@vueuse/nuxt",
|
||||
"@formkit/auto-animate/nuxt"
|
||||
"@formkit/auto-animate/nuxt",
|
||||
"@vite-pwa/nuxt",
|
||||
],
|
||||
|
||||
pwa: {
|
||||
strategies: "injectManifest",
|
||||
srcDir: "public",
|
||||
filename: "sw.js",
|
||||
manifest: {
|
||||
name: "Heymlz",
|
||||
short_name: "Heymlz",
|
||||
theme_color: "#ffffff",
|
||||
icons: [
|
||||
{
|
||||
src: "/logo/logo-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "/logo/logo-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
},
|
||||
],
|
||||
},
|
||||
workbox: {
|
||||
navigateFallback: "/",
|
||||
clientsClaim: true,
|
||||
skipWaiting: true,
|
||||
},
|
||||
devOptions: { enabled: true, type: "module" },
|
||||
},
|
||||
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
API_BASE_URL: "https://api.heymlz.com",
|
||||
|
||||
Generated
-11995
File diff suppressed because it is too large
Load Diff
+52
-50
@@ -1,52 +1,54 @@
|
||||
{
|
||||
"name": "nuxt-app",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node .output/server/index.mjs",
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"dev-network": "nuxi dev --host",
|
||||
"dev-o": "nuxt dev -- -o",
|
||||
"test": "vitest",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@nuxt/icon": "^1.10.3",
|
||||
"@nuxtjs/google-fonts": "^3.2.0",
|
||||
"@tanstack/vue-query": "^5.66.9",
|
||||
"@tanstack/vue-query-devtools": "^5.66.9",
|
||||
"@vuelidate/core": "^2.0.3",
|
||||
"@vuelidate/validators": "^2.0.4",
|
||||
"@vueuse/integrations": "^12.7.0",
|
||||
"@vueuse/nuxt": "^12.7.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.8.1",
|
||||
"date-fns-jalali": "^4.1.0-0",
|
||||
"fast-average-color": "^9.4.0",
|
||||
"gsap": "^3.12.7",
|
||||
"isomorphic-dompurify": "^2.22.0",
|
||||
"jalali-ts": "^8.0.0",
|
||||
"masonry-layout": "^4.2.2",
|
||||
"nuxt": "^3.15.4",
|
||||
"reka-ui": "^1.0.0-alpha.6",
|
||||
"swiper": "^11.2.4",
|
||||
"universal-cookie": "^7.2.2",
|
||||
"vue": "latest",
|
||||
"vue-router": "latest",
|
||||
"vue-scrollto": "^2.20.0",
|
||||
"vue-skeletor": "^1.0.6",
|
||||
"vue3-marquee": "^4.2.2",
|
||||
"vue3-persian-datetime-picker": "^1.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.0.9",
|
||||
"@types/masonry-layout": "^4.2.8",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.5.3",
|
||||
"tailwindcss": "^4.0.9"
|
||||
}
|
||||
"name": "nuxt-app",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node .output/server/index.mjs",
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"dev-network": "nuxi dev --host",
|
||||
"dev-o": "nuxt dev -- -o",
|
||||
"test": "vitest",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@nuxt/icon": "^1.10.3",
|
||||
"@nuxtjs/google-fonts": "^3.2.0",
|
||||
"@tanstack/vue-query": "^5.62.2",
|
||||
"@tanstack/vue-query-devtools": "^5.62.3",
|
||||
"@vite-pwa/nuxt": "^0.10.6",
|
||||
"@vuelidate/core": "^2.0.3",
|
||||
"@vuelidate/validators": "^2.0.4",
|
||||
"@vueuse/integrations": "^12.7.0",
|
||||
"@vueuse/nuxt": "^12.7.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.8.1",
|
||||
"date-fns-jalali": "^4.1.0-0",
|
||||
"fast-average-color": "^9.4.0",
|
||||
"gsap": "^3.12.7",
|
||||
"isomorphic-dompurify": "^2.22.0",
|
||||
"jalali-ts": "^8.0.0",
|
||||
"masonry-layout": "^4.2.2",
|
||||
"nuxt": "^3.15.4",
|
||||
"reka-ui": "^1.0.0-alpha.6",
|
||||
"swiper": "^11.2.4",
|
||||
"universal-cookie": "^7.2.2",
|
||||
"vue": "latest",
|
||||
"vue-router": "latest",
|
||||
"vue-scrollto": "^2.20.0",
|
||||
"vue-skeletor": "^1.0.6",
|
||||
"vue3-marquee": "^4.2.2",
|
||||
"vue3-persian-datetime-picker": "^1.2.2",
|
||||
"workbox-window": "^7.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.0.9",
|
||||
"@types/masonry-layout": "^4.2.8",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.5.3",
|
||||
"tailwindcss": "^4.0.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Workbox } from "workbox-window";
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
const updateAvailable = ref(false);
|
||||
let wb: Workbox | null = null;
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
wb = new Workbox("/sw.js");
|
||||
|
||||
const isStandalone = window.matchMedia(
|
||||
"(display-mode: standalone)"
|
||||
).matches;
|
||||
const isIOSPWA = (window.navigator as any).standalone;
|
||||
const isInstalledAsPWA = isStandalone || isIOSPWA;
|
||||
|
||||
// Listen for messages from the service worker
|
||||
navigator.serviceWorker.addEventListener("message", (event) => {
|
||||
if (
|
||||
event.data &&
|
||||
event.data.type === "VERSION_CHECK"
|
||||
) {
|
||||
checkForUpdate(event.data.version);
|
||||
}
|
||||
});
|
||||
|
||||
// Register the service worker and check if there's already a waiting one
|
||||
wb.register().then((registration: any) => {
|
||||
if (registration.waiting) {
|
||||
checkForUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 🔹 Function to compare versions and show update modal if needed
|
||||
const checkForUpdate = (newVersion?: string) => {
|
||||
const currentVersion = localStorage.getItem("pwa_version");
|
||||
|
||||
if (newVersion && currentVersion !== newVersion) {
|
||||
updateAvailable.value = true;
|
||||
localStorage.setItem("pwa_version", newVersion);
|
||||
}
|
||||
};
|
||||
|
||||
// 🔹 Function to apply the update
|
||||
const handleUpdate = () => {
|
||||
wb?.messageSW({ type: "SKIP_WAITING" });
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return {
|
||||
provide: {
|
||||
updateAvailable,
|
||||
handleUpdate,
|
||||
},
|
||||
};
|
||||
});
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 210 KiB |
@@ -0,0 +1,64 @@
|
||||
import { precacheAndRoute } from "workbox-precaching";
|
||||
|
||||
// Precaching configuration for PWA assets
|
||||
precacheAndRoute(self.__WB_MANIFEST);
|
||||
|
||||
// Version
|
||||
|
||||
const VERSION = "1.0.911";
|
||||
|
||||
// Service Worker Installation
|
||||
self.addEventListener("install", (event) => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
// Service Worker Activation
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
const clients = await self.clients.matchAll({ type: "window" });
|
||||
|
||||
// Notify all open clients about the version
|
||||
clients.forEach((client) =>
|
||||
client.postMessage({ type: "VERSION_CHECK", version: VERSION })
|
||||
);
|
||||
|
||||
self.clients.claim();
|
||||
console.log("Service Worker Activated (Version: " + VERSION + ")");
|
||||
})()
|
||||
);
|
||||
});
|
||||
|
||||
// Push Notification Handler for Django Web Push
|
||||
self.addEventListener("push", (event) => {
|
||||
try {
|
||||
const payload = event.data?.json() || {
|
||||
title: "New Notification",
|
||||
body: "You have a new message",
|
||||
icon: "/logo-192x192.png",
|
||||
data: { url: "/" },
|
||||
};
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(payload.title, {
|
||||
body: payload.body,
|
||||
icon: payload.icon || "/logo-192x192.png",
|
||||
data: payload.data,
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Push handling failed:", error);
|
||||
}
|
||||
});
|
||||
|
||||
// Notification Click Handler
|
||||
self.addEventListener("notificationclick", (event) => {
|
||||
event.notification.close();
|
||||
event.waitUntil(clients.openWindow(event.notification.data?.url || "/"));
|
||||
});
|
||||
|
||||
self.addEventListener("message", (event) => {
|
||||
if (event.data === "SKIP_WAITING") {
|
||||
self.skipWaiting();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user