merge
This commit is contained in:
+3
-1
@@ -28,4 +28,6 @@ REFRESH_TOKEN_LIFETIME = 5000
|
||||
|
||||
SMS_API_KEY = ''
|
||||
|
||||
VAPID_PRIVATE_KEY = 'NajogmGTsGsZ_dfURrjUpgsm5fui-s5AzruBQgMh_I4'
|
||||
VAPID_PRIVATE_KEY = 'NajogmGTsGsZ_dfURrjUpgsm5fui-s5AzruBQgMh_I4'
|
||||
|
||||
OPENAI_API_KEY = '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'
|
||||
+179
-147
@@ -1,5 +1,4 @@
|
||||
from dotenv import load_dotenv
|
||||
# from http.cookiejar import debug
|
||||
from pathlib import Path
|
||||
from datetime import timedelta
|
||||
import os
|
||||
@@ -7,15 +6,21 @@ from django.templatetags.static import static
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv(".env.local")
|
||||
|
||||
# ==============================================================================
|
||||
# General Configuration
|
||||
# ==============================================================================
|
||||
DOMAIN = os.getenv("DOMAIN")
|
||||
API_DOMAIN = os.getenv("API_DOMAIN")
|
||||
OPENAI_API_KEY = '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'
|
||||
# TODO update telegram bot token
|
||||
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
||||
|
||||
# TODO update email bullshit
|
||||
# API Keys and Tokens
|
||||
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
||||
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
||||
VAPID_PRIVATE_KEY = os.getenv("VAPID_PRIVATE_KEY")
|
||||
|
||||
# Email Configuration
|
||||
EMAIL_BACKEND = os.getenv("EMAIL_BACKEND")
|
||||
EMAIL_HOST = os.getenv("EMAIL_HOST")
|
||||
EMAIL_PORT = 587
|
||||
@@ -24,17 +29,16 @@ EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER")
|
||||
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD")
|
||||
DEFAULT_FROM_EMAIL = os.getenv("SECRET_KEY")
|
||||
|
||||
# Security and Debugging
|
||||
SECRET_KEY = os.getenv("SECRET_KEY")
|
||||
DEBUG = True
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
|
||||
|
||||
VAPID_PRIVATE_KEY = os.getenv('VAPID_PRIVATE_KEY')
|
||||
|
||||
# Application definition
|
||||
|
||||
# ==============================================================================
|
||||
# Application Definition
|
||||
# ==============================================================================
|
||||
INSTALLED_APPS = [
|
||||
# unfold theme
|
||||
# Unfold Theme
|
||||
"unfold",
|
||||
"unfold.contrib.filters",
|
||||
"unfold.contrib.forms",
|
||||
@@ -42,37 +46,159 @@ INSTALLED_APPS = [
|
||||
"unfold.contrib.import_export",
|
||||
"unfold.contrib.guardian",
|
||||
"unfold.contrib.simple_history",
|
||||
# django
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
# thired party apps
|
||||
'corsheaders',
|
||||
'rest_framework',
|
||||
'drf_spectacular',
|
||||
'django_cleanup.apps.CleanupConfig',
|
||||
'django_filters',
|
||||
'rest_framework_simplejwt',
|
||||
'rest_framework_simplejwt.token_blacklist',
|
||||
'rest_framework.authtoken',
|
||||
'import_export',
|
||||
# Django Core
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
# Third-Party Apps
|
||||
"storages",
|
||||
"corsheaders",
|
||||
"rest_framework",
|
||||
"drf_spectacular",
|
||||
"django_cleanup.apps.CleanupConfig",
|
||||
"django_filters",
|
||||
"rest_framework_simplejwt",
|
||||
"rest_framework_simplejwt.token_blacklist",
|
||||
"rest_framework.authtoken",
|
||||
"import_export",
|
||||
"django_jalali",
|
||||
# custom apps
|
||||
'product',
|
||||
'account',
|
||||
'ticket',
|
||||
'chat',
|
||||
'order',
|
||||
'home',
|
||||
'blog',
|
||||
|
||||
# Custom Apps
|
||||
"product",
|
||||
"account",
|
||||
"ticket",
|
||||
"chat",
|
||||
"order",
|
||||
"home",
|
||||
"blog",
|
||||
]
|
||||
|
||||
# ==============================================================================
|
||||
# Middleware Configuration
|
||||
# ==============================================================================
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"corsheaders.middleware.CorsMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "core.urls"
|
||||
|
||||
# ==============================================================================
|
||||
# Template Configuration
|
||||
# ==============================================================================
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [BASE_DIR / "templates"],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = "core.wsgi.application"
|
||||
|
||||
# ==============================================================================
|
||||
# Authentication and Password Validation
|
||||
# ==============================================================================
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||
},
|
||||
]
|
||||
|
||||
LANGUAGE_CODE = "fa"
|
||||
TIME_ZONE = "UTC"
|
||||
USE_I18N = True
|
||||
USE_L10N = True
|
||||
USE_TZ = True
|
||||
|
||||
# ==============================================================================
|
||||
# Static Files Configuration
|
||||
# ==============================================================================
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, "custom_static"),
|
||||
BASE_DIR / "core" / "static",
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
AUTH_USER_MODEL = "account.User"
|
||||
|
||||
# ==============================================================================
|
||||
# REST Framework Configuration
|
||||
# ==============================================================================
|
||||
REST_FRAMEWORK = {
|
||||
# "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
|
||||
"DEFAULT_RENDERER_CLASSES": [
|
||||
"rest_framework.renderers.JSONRenderer",
|
||||
"rest_framework.renderers.BrowsableAPIRenderer",
|
||||
],
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||
"rest_framework_simplejwt.authentication.JWTAuthentication",
|
||||
),
|
||||
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
||||
# "DEFAULT_PERMISSION_CLASSES": [
|
||||
# "rest_framework.permissions.IsAuthenticated",
|
||||
# ],
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# Simple JWT Configuration
|
||||
# ==============================================================================
|
||||
SIMPLE_JWT = {
|
||||
"ACCESS_TOKEN_LIFETIME": timedelta(days=1),
|
||||
"REFRESH_TOKEN_LIFETIME": timedelta(days=7),
|
||||
"ROTATE_REFRESH_TOKENS": True,
|
||||
"BLACKLIST_AFTER_ROTATION": True,
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# Spectacular (API Documentation) Configuration
|
||||
# ==============================================================================
|
||||
SPECTACULAR_SETTINGS = {
|
||||
"TITLE": os.getenv("SITE_TITLE"),
|
||||
"DESCRIPTION": os.getenv("SITE_TITLE"),
|
||||
"VERSION": "2.0.0",
|
||||
"SERVE_INCLUDE_SCHEMA": False,
|
||||
"COMPONENT_SPLIT_REQUEST": True,
|
||||
"SWAGGER_UI_SETTINGS": {
|
||||
"persistAuthorization": True,
|
||||
},
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# Persian Datetime Configuration
|
||||
# ==============================================================================
|
||||
JALALI_SETTINGS = {
|
||||
# JavaScript static files for the admin Jalali date widget
|
||||
"ADMIN_JS_STATIC_FILES": [
|
||||
"admin/jquery.ui.datepicker.jalali/scripts/jquery-1.10.2.min.js",
|
||||
"admin/jquery.ui.datepicker.jalali/scripts/jquery.ui.core.js",
|
||||
@@ -81,7 +207,6 @@ JALALI_SETTINGS = {
|
||||
"admin/jquery.ui.datepicker.jalali/scripts/jquery.ui.datepicker-cc-fa.js",
|
||||
"admin/main.js",
|
||||
],
|
||||
# CSS static files for the admin Jalali date widget
|
||||
"ADMIN_CSS_STATIC_FILES": {
|
||||
"all": [
|
||||
"admin/jquery.ui.datepicker.jalali/themes/base/jquery-ui.min.css",
|
||||
@@ -91,113 +216,20 @@ JALALI_SETTINGS = {
|
||||
}
|
||||
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'core.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [BASE_DIR / "templates"],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'core.wsgi.application'
|
||||
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
LANGUAGE_CODE = 'fa'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
USE_L10N = True
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, 'custom_static'),
|
||||
BASE_DIR / "core" / "static"
|
||||
]
|
||||
|
||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
AUTH_USER_MODEL = 'account.User'
|
||||
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
# 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
|
||||
'DEFAULT_RENDERER_CLASSES': [
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
'rest_framework.renderers.BrowsableAPIRenderer',
|
||||
|
||||
],
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
|
||||
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||
|
||||
),
|
||||
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
|
||||
# 'DEFAULT_PERMISSION_CLASSES': [
|
||||
# 'rest_framework.permissions.IsAuthenticated',
|
||||
# ],
|
||||
# ==============================================================================
|
||||
# AWS S3 setting for production
|
||||
# ==============================================================================
|
||||
AWS_ACCESS_KEY_ID = 'mtiSN2JWjWgyfr2u'
|
||||
AWS_SECRET_ACCESS_KEY = 'ZGmOM6ekLJEswJS1kOLp49B8DQ3GT0HZ'
|
||||
AWS_STORAGE_BUCKET_NAME = 'c262408'
|
||||
AWS_S3_ENDPOINT_URL = 'https://parspack.net'
|
||||
AWS_S3_REGION_NAME = 'default'
|
||||
AWS_S3_SIGNATURE_VERSION = 's3'
|
||||
AWS_S3_ADDRESSING_STYLE = 'virtual'
|
||||
AWS_QUERYSTRING_AUTH = False
|
||||
AWS_DEFAULT_ACL = None
|
||||
AWS_S3_OBJECT_PARAMETERS = {
|
||||
'CacheControl': 'max-age=86400',
|
||||
'ACL': 'public-read',
|
||||
}
|
||||
|
||||
SIMPLE_JWT = {
|
||||
'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
|
||||
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
|
||||
'ROTATE_REFRESH_TOKENS': True,
|
||||
'BLACKLIST_AFTER_ROTATION': True,
|
||||
}
|
||||
|
||||
SPECTACULAR_SETTINGS = {
|
||||
'TITLE': os.getenv("SITE_TITLE"),
|
||||
'DESCRIPTION': os.getenv("SITE_TITLE"),
|
||||
'VERSION': '2.0.0',
|
||||
'SERVE_INCLUDE_SCHEMA': False,
|
||||
'COMPONENT_SPLIT_REQUEST': True
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
def environment_callback(request):
|
||||
return ["نسخه ی توسعه", "success"]
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from .base import *
|
||||
from .unfold_conf import *
|
||||
|
||||
|
||||
ALLOWED_HOSTS = ['127.0.0.1', 'localhost', DOMAIN, API_DOMAIN]
|
||||
CSRF_TRUSTED_ORIGINS = [
|
||||
f"https://{DOMAIN}",
|
||||
@@ -31,7 +33,16 @@ DATABASES = {
|
||||
}
|
||||
}
|
||||
|
||||
MEDIA_URL = '/shop_media/'
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": 'core.storages.MediaStorage',
|
||||
},
|
||||
"staticfiles": {
|
||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||
},
|
||||
}
|
||||
|
||||
MEDIA_URL = 'https://c262408.parspack.net/'
|
||||
MEDIA_ROOT = '/app/media'
|
||||
|
||||
STATIC_URL = '/shop_static/'
|
||||
|
||||
@@ -264,4 +264,7 @@ UNFOLD = {
|
||||
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
def environment_callback(request):
|
||||
return ["نسخه ی توسعه", "success"]
|
||||
@@ -0,0 +1,6 @@
|
||||
from storages.backends.s3boto3 import S3Boto3Storage
|
||||
|
||||
|
||||
class MediaStorage(S3Boto3Storage):
|
||||
location = 'media'
|
||||
default_acl = 'public-read'
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-26 17:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0012_alter_learnvideomodel_content_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='showcaseslider',
|
||||
name='description',
|
||||
field=models.CharField(max_length=150, verbose_name='توضیحات'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-26 19:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0013_alter_showcaseslider_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='homeimagemodel',
|
||||
name='video1',
|
||||
field=models.FileField(null=True, upload_to='diff_section/', verbose_name='ویدیو اول'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='homeimagemodel',
|
||||
name='video2',
|
||||
field=models.FileField(null=True, upload_to='diff_section/', verbose_name='ویدیو دوم'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='homeimagemodel',
|
||||
name='image1',
|
||||
field=models.ImageField(upload_to='diff_section/', verbose_name='عکس اول'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='homeimagemodel',
|
||||
name='image2',
|
||||
field=models.ImageField(upload_to='diff_section/', verbose_name='عکس دوم'),
|
||||
),
|
||||
]
|
||||
@@ -18,18 +18,22 @@ class SliderModel(models.Model):
|
||||
|
||||
|
||||
class HomeImageModel(models.Model):
|
||||
image1 = models.ImageField(upload_to='diff_image/', verbose_name='عکس اول')
|
||||
image2 = models.ImageField(upload_to='diff_image/', verbose_name='عکس دوم')
|
||||
image1 = models.ImageField(upload_to='diff_section/', verbose_name='عکس اول')
|
||||
image2 = models.ImageField(upload_to='diff_section/', verbose_name='عکس دوم')
|
||||
title1 = models.CharField(max_length=50, verbose_name='عنوان عکس اول')
|
||||
title2 = models.CharField(max_length=50, verbose_name='عنوان عکس دوم')
|
||||
description1 = models.TextField(verbose_name='توضیحات عکس اول')
|
||||
description2 = models.TextField(verbose_name='توضیحات عکس دوم')
|
||||
link1 = models.URLField(verbose_name='لینک عکس اول')
|
||||
link2 = models.URLField(verbose_name='لینک عکس دوم')
|
||||
video1 = models.FileField(verbose_name='ویدیو اول', upload_to='diff_section/', null=True)
|
||||
video2 = models.FileField(verbose_name='ویدیو دوم', upload_to='diff_section/', null=True)
|
||||
unique = (('unique', 'unique'),)
|
||||
unique_filed = models.CharField(max_length=20, choices=unique, unique=True, default='unique', verbose_name='یونیک فیلد')
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.title1} - {self.title2}'
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'مدل عکس تفاوت خانه'
|
||||
verbose_name_plural = 'مدل عکس تفاوت خانه'
|
||||
@@ -37,7 +41,7 @@ class HomeImageModel(models.Model):
|
||||
|
||||
class ShowCaseSlider(models.Model):
|
||||
title = models.CharField(max_length=30, verbose_name='عنوان')
|
||||
description = models.CharField(max_length=150, verbose_name='عنوان')
|
||||
description = models.CharField(max_length=150, verbose_name='توضیحات')
|
||||
link = models.URLField(verbose_name='لینک')
|
||||
image = models.ImageField(upload_to='show_case/', verbose_name='عکس')
|
||||
def __str__(self):
|
||||
|
||||
+11
-9
@@ -59,21 +59,23 @@ class OrderModel(models.Model):
|
||||
print('didnt send')
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def total_with_discount(self):
|
||||
total_with_item_discount = sum(item.total_with_discount() for item in self.items.all())
|
||||
if self.discount_code:
|
||||
if not self.discount_code.is_valid():
|
||||
raise DiscountNotAvailableError('این کد تخفیف دیگر معتبر نیست')
|
||||
discount_percent = self.discount_code.percent
|
||||
return total_with_item_discount * ((100 - discount_percent) / 100)
|
||||
return total_with_item_discount
|
||||
def discount(self):
|
||||
pass
|
||||
# total_with_item_discount = sum(item.total_with_discount() for item in self.items.all())
|
||||
# if self.discount_code:
|
||||
# if not self.discount_code.is_valid():
|
||||
# raise DiscountNotAvailableError('این کد تخفیف دیگر معتبر نیست')
|
||||
# discount_percent = self.discount_code.percent
|
||||
# return total_with_item_discount * ((100 - discount_percent) / 100)
|
||||
# return total_with_item_discount
|
||||
|
||||
|
||||
def tax(self):
|
||||
return self.total_without_tax() * 0.2
|
||||
|
||||
def total(self):
|
||||
return self.total_with_discount() + self.tax()
|
||||
pass
|
||||
# return self.total_with_discount() + self.tax()
|
||||
|
||||
def remove_order_item(self, item_pk, quantity):
|
||||
pass
|
||||
|
||||
@@ -8,8 +8,20 @@ class OrderItemSerailzier(serializers.ModelSerializer):
|
||||
fields = "__all__"
|
||||
read_only_fields = ('order', 'product')
|
||||
|
||||
class OrderModelSerializer(serializers.ModelSerializer):
|
||||
class CartSerializer(serializers.ModelSerializer):
|
||||
items = OrderItemSerailzier(many=True)
|
||||
class Meta:
|
||||
model = OrderModel
|
||||
fields = ['address', 'created_at', 'is_paid', 'status', 'discount_code', 'items']
|
||||
fields = ['address', 'created_at', 'is_paid', 'status', 'discount_code', 'items']
|
||||
|
||||
|
||||
class OrderSerializer(serializers.ModelSerializer):
|
||||
count = serializers.SerializerMethodField()
|
||||
images = serializers.SerializerMethodField()
|
||||
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"]
|
||||
@@ -1,9 +1,10 @@
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from .views import CartItemViews, CartView
|
||||
from .views import CartItemViews, CartView, OrderlistView
|
||||
|
||||
urlpatterns = [
|
||||
path('list', OrderlistView.as_view(), name='order-list'),
|
||||
path('cart', CartView.as_view()),
|
||||
path('cart/item/<int:pk>', CartItemViews.as_view(), name='change-item-cart'),
|
||||
# path('payment', CartView.as_view()),
|
||||
|
||||
+12
-7
@@ -4,7 +4,7 @@ from rest_framework.views import APIView, Response
|
||||
from django.shortcuts import get_object_or_404
|
||||
from product.models import ProductVariant
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from .serializers import OrderItemSerailzier, OrderModelSerializer
|
||||
from .serializers import *
|
||||
# from cart.models import
|
||||
from rest_framework import status
|
||||
from .models import OrderItemModel, OrderModel
|
||||
@@ -53,11 +53,6 @@ class CartItemViews(APIView):
|
||||
user=request.user,
|
||||
status='CART'
|
||||
)
|
||||
# order_item, created = OrderItemModel.objects.get_or_create(
|
||||
# order=cart_order,
|
||||
# product=product_variant,
|
||||
# defaults={'quantity': request.data.get('quantity', 1)}
|
||||
# )
|
||||
order_item = get_object_or_404(OrderItemModel, order=cart_order, product=product_variant)
|
||||
order_item.delete()
|
||||
return Response({'detail': f'محصول {product_variant.product.name} از سبد خرید پاک شد'}, status=status.HTTP_204_NO_CONTENT)
|
||||
@@ -66,9 +61,19 @@ class CartItemViews(APIView):
|
||||
|
||||
class CartView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = OrderModelSerializer
|
||||
serializer_class = CartSerializer
|
||||
def get(self, request):
|
||||
user = request.user
|
||||
cart_instance, created = OrderModel.objects.get_or_create(user=user, status='CART')
|
||||
cart_ser = self.serializer_class(instance=cart_instance, context={'request': request})
|
||||
return Response(cart_ser.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class OrderlistView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = OrderSerializer
|
||||
def get(self, request):
|
||||
user = request.user
|
||||
orders = OrderModel.objects.filter(user=user).exclude(status="CART")
|
||||
orders_ser = self.serializer_class(instance=orders, many=True, context={'request': request})
|
||||
return Response(orders_ser.data, status=status.HTTP_200_OK)
|
||||
@@ -170,14 +170,14 @@ class AllProductsView(APIView):
|
||||
if search_query:
|
||||
products = products.filter(Q(name__icontains=search_query) | Q(description__icontains=search_query))
|
||||
|
||||
# Price filters
|
||||
price_gte = request.query_params.get('price_gte', None)
|
||||
price_lte = request.query_params.get('price_lte', None)
|
||||
# # Price filters
|
||||
# price_gte = request.query_params.get('price_gte', None)
|
||||
# price_lte = request.query_params.get('price_lte', None)
|
||||
|
||||
if price_gte:
|
||||
products = products.filter(variants__min_price__gte=price_gte)
|
||||
if price_lte:
|
||||
products = products.filter(variants__min_price__lte=price_lte)
|
||||
# if price_gte:
|
||||
# products = products.filter(variants__min_price__gte=price_gte)
|
||||
# if price_lte:
|
||||
# products = products.filter(variants__min_price__lte=price_lte)
|
||||
|
||||
# Sorting
|
||||
sort_by = request.query_params.get('sort', None)
|
||||
|
||||
@@ -5,22 +5,33 @@ annotated-types==0.7.0
|
||||
anyio==4.6.0
|
||||
asgiref==3.8.1
|
||||
attrs==24.2.0
|
||||
az-iranian-bank-gateways==2.0.5
|
||||
beautifulsoup4==4.12.3
|
||||
boto3==1.36.26
|
||||
botocore==1.36.26
|
||||
branca==0.8.1
|
||||
certifi==2024.8.30
|
||||
cffi==1.17.1
|
||||
charset-normalizer==3.3.2
|
||||
colorama==0.4.6
|
||||
cryptography==44.0.1
|
||||
defusedxml==0.8.0rc2
|
||||
diff-match-patch==20230430
|
||||
distro==1.9.0
|
||||
Django==5.1.2
|
||||
django-admin-interface==0.28.5
|
||||
django-admin-persian-fonts==0.2
|
||||
django-cleanup==8.1.0
|
||||
django-colorfield==0.11.0
|
||||
django-cors-headers==4.4.0
|
||||
django-cron==0.6.0
|
||||
django-dbbackup==4.2.1
|
||||
django-dirtyfields==1.9.3
|
||||
django-filter==24.3
|
||||
django-import-export==4.1.1
|
||||
django-iranian-cities==1.0.2
|
||||
django-jalali==7.3.0
|
||||
django-storages==1.14.5
|
||||
django-unfold==0.48.0
|
||||
djangorestframework==3.15.2
|
||||
djangorestframework-simplejwt==5.3.1
|
||||
@@ -28,6 +39,7 @@ djoser==2.3.1
|
||||
dnspython==2.7.0
|
||||
drf-spectacular==0.27.2
|
||||
email_validator==2.2.0
|
||||
et-xmlfile==1.1.0
|
||||
factory_boy==3.3.1
|
||||
Faker==28.4.1
|
||||
folium==0.19.4
|
||||
@@ -43,29 +55,40 @@ httpcore==1.0.5
|
||||
httpx==0.27.2
|
||||
idna==3.10
|
||||
inflection==0.5.1
|
||||
isodate==0.6.1
|
||||
jalali_core==1.0.0
|
||||
jdatetime==5.0.0
|
||||
Jinja2==3.1.5
|
||||
jiter==0.8.2
|
||||
jmespath==1.0.1
|
||||
jsonschema==4.23.0
|
||||
jsonschema-specifications==2024.10.1
|
||||
lxml==5.2.2
|
||||
MarkupPy==1.14
|
||||
MarkupSafe==3.0.2
|
||||
maxminddb==2.6.2
|
||||
multidict==6.1.0
|
||||
numpy==2.2.3
|
||||
oauthlib==3.2.2
|
||||
odfpy==1.4.1
|
||||
openai==1.58.1
|
||||
openpyxl==3.1.2
|
||||
pillow==10.4.0
|
||||
platformdirs==4.2.2
|
||||
propcache==0.2.0
|
||||
psutil==6.0.0
|
||||
psycopg2-binary==2.9.10
|
||||
py-vapid==1.9.2
|
||||
pycparser==2.22
|
||||
pycryptodome==3.20.0
|
||||
pydantic==2.10.6
|
||||
pydantic_core==2.27.2
|
||||
PyJWT==2.10.1
|
||||
pyTelegramBotAPI==4.23.0
|
||||
python-dateutil==2.9.0.post0
|
||||
python-decouple==3.8
|
||||
python-dotenv==1.0.1
|
||||
python-slugify==8.0.4
|
||||
python-telegram-bot==21.6
|
||||
python3-openid==3.2.0
|
||||
pytz==2024.2
|
||||
@@ -73,20 +96,29 @@ pywebpush==2.0.3
|
||||
PyYAML==6.0.2
|
||||
referencing==0.35.1
|
||||
requests==2.32.3
|
||||
requests-file==2.1.0
|
||||
requests-oauthlib==2.0.0
|
||||
requests-toolbelt==1.0.0
|
||||
rpds-py==0.20.0
|
||||
s3transfer==0.11.2
|
||||
setuptools==75.1.0
|
||||
six==1.16.0
|
||||
sniffio==1.3.1
|
||||
social-auth-app-django==5.4.2
|
||||
social-auth-core==4.5.4
|
||||
soupsieve==2.5
|
||||
sqlparse==0.5.1
|
||||
tablib==3.5.0
|
||||
telebot==0.0.5
|
||||
text-unidecode==1.3
|
||||
tqdm==4.67.1
|
||||
typing_extensions==4.12.2
|
||||
tzdata==2024.1
|
||||
uritemplate==4.1.1
|
||||
urllib3==2.2.3
|
||||
whitenoise==6.7.0
|
||||
xlrd==2.0.1
|
||||
xlwt==1.3.0
|
||||
xyzservices==2025.1.0
|
||||
yarl==1.11.1
|
||||
zeep==4.2.1
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 5.1.2 on 2025-02-26 17:42
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ticket', '0014_attachment_uploaded_by'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='ticket',
|
||||
name='attachments',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='ticket',
|
||||
name='content',
|
||||
),
|
||||
]
|
||||
@@ -37,7 +37,6 @@ class Ticket(models.Model):
|
||||
('other', 'سایر'),
|
||||
]
|
||||
subject = models.CharField(max_length=255, verbose_name='موضوع')
|
||||
content = models.TextField(verbose_name='جزيیات تیکت')
|
||||
ticket_category = models.CharField(max_length=30, verbose_name='دسته بندی تیکت', choices=CATEGORY_CHOICES)
|
||||
customer = models.ForeignKey(User, on_delete=models.CASCADE, related_name="tickets", verbose_name='کاربر')
|
||||
admin = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="assigned_tickets", verbose_name='ادمین')
|
||||
@@ -45,7 +44,6 @@ class Ticket(models.Model):
|
||||
created_at = jmodels.jDateTimeField(auto_now_add=True, verbose_name='ساخته شده در')
|
||||
updated_at = jmodels.jDateTimeField(auto_now=True, verbose_name='اپدیت شده در')
|
||||
order = models.ForeignKey(OrderModel ,blank=True, null=True, on_delete=models.SET_NULL)
|
||||
attachments = models.ManyToManyField(Attachment, related_name='tickets', blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.subject
|
||||
|
||||
@@ -3,40 +3,55 @@ from .models import Ticket, Message, Attachment
|
||||
from django.utils.timezone import localtime
|
||||
from account.serializers import ProfileSerializer
|
||||
|
||||
|
||||
|
||||
class MessageSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Message
|
||||
fields = '__all__'
|
||||
|
||||
class TicketSerializer(serializers.ModelSerializer):
|
||||
messages = MessageSerializer(many=True, read_only=True)
|
||||
admin = ProfileSerializer(read_only=True)
|
||||
class Meta:
|
||||
model = Ticket
|
||||
exclude = ('customer', )
|
||||
read_only_fields = ('status', 'admin', )
|
||||
|
||||
|
||||
class TicketListSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Ticket
|
||||
exclude = ('customer', 'admin', 'order', 'content')
|
||||
read_only_fields = ('status',)
|
||||
|
||||
|
||||
class AttachmentSerializer(serializers.ModelSerializer):
|
||||
file = serializers.FileField(write_only=True)
|
||||
link = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Attachment
|
||||
fields = ['id', 'name', 'file','link' , 'created_at', 'size']
|
||||
fields = ['id', 'name', 'file', 'link' , 'created_at', 'size']
|
||||
read_only_fields = ('size', 'name', )
|
||||
|
||||
def get_link(self, obj):
|
||||
request = self.context.get('request')
|
||||
if request is not None:
|
||||
return request.build_absolute_uri(obj.file.url)
|
||||
return obj.file.url
|
||||
return obj.file.url
|
||||
|
||||
class MessageSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Message
|
||||
exclude = ('sender', )
|
||||
extra_kwargs = {'ticket': {'write_only': True}}
|
||||
|
||||
class MessageForTicketSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Message
|
||||
exclude = ('sender', 'ticket')
|
||||
class TicketSerializer(serializers.ModelSerializer):
|
||||
messages = MessageSerializer(many=True, read_only=True)
|
||||
message = MessageForTicketSerializer(write_only=True)
|
||||
class Meta:
|
||||
model = Ticket
|
||||
exclude = ('customer', 'admin')
|
||||
read_only_fields = ('status',)
|
||||
|
||||
def create(self, validated_data):
|
||||
message = validated_data.pop('message', None)
|
||||
ticket = super().create(validated_data)
|
||||
print(f'fck this shit{ticket.pk}')
|
||||
message['ticket'] = ticket.pk
|
||||
message_obj = MessageSerializer(data=message)
|
||||
if message_obj.is_valid():
|
||||
message_obj.save(sender=ticket.customer)
|
||||
return ticket
|
||||
else:
|
||||
raise Exception('bullshit data for message')
|
||||
|
||||
class TicketListSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Ticket
|
||||
exclude = ('customer', 'admin', )
|
||||
read_only_fields = ('status',)
|
||||
|
||||
|
||||
|
||||
+22
-8
@@ -40,16 +40,30 @@ class AttachmentUploadView(APIView):
|
||||
|
||||
|
||||
|
||||
class TicketCreateView(generics.CreateAPIView):
|
||||
queryset = Ticket.objects.all()
|
||||
# class TicketCreateView(generics.CreateAPIView):
|
||||
# queryset = Ticket.objects.all()
|
||||
# serializer_class = TicketSerializer
|
||||
# permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
# def perform_create(self, serializer):
|
||||
# message = serializer.validated_data.get('message')
|
||||
# ticket = serializer.validated_data.get('ticket')
|
||||
# serializer.save(customer=self.request.user)
|
||||
|
||||
|
||||
class TicketCreateView(APIView):
|
||||
serializer_class = TicketSerializer
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(customer=self.request.user)
|
||||
|
||||
|
||||
|
||||
def post(self, request):
|
||||
new_ticket_ser = self.serializer_class(data=request.data)
|
||||
message = request.data.get('message', None)
|
||||
print(message)
|
||||
if new_ticket_ser.is_valid():
|
||||
new_ticket_ser.save(customer=request.user)
|
||||
return Response(new_ticket_ser.data, status=status.HTTP_201_CREATED)
|
||||
else:
|
||||
print(new_ticket_ser.error_messages)
|
||||
return Response(new_ticket_ser.errors)
|
||||
class TicketListView(APIView):
|
||||
serializer_class = TicketListSerializer
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/bin/sh
|
||||
|
||||
|
||||
. /etc/profile
|
||||
|
||||
|
||||
echo "Cron environment:" >> /var/log/cron/cron.log
|
||||
env >> /var/log/cron/cron.log
|
||||
|
||||
|
||||
echo "Starting backup at $(date)" >> /var/log/cron/cron.log
|
||||
|
||||
export PGPASSWORD=$PG_PASSWORD
|
||||
|
||||
TELEGRAM_API="https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument"
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="backup_heymlz_shop${TIMESTAMP}.sql"
|
||||
ZIP_FILE="backup_heymlz_shop${TIMESTAMP}.zip"
|
||||
|
||||
|
||||
pg_dump -h $PG_HOST -p $PG_PORT -U $PG_USER -d $PG_DATABASE -w > /backups/$BACKUP_FILE
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "backup failed" >> /var/log/cron/cron.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
zip -j /backups/$ZIP_FILE /backups/$BACKUP_FILE
|
||||
curl -F chat_id=$TELEGRAM_CHAT_ID \
|
||||
-F document=@/backups/$ZIP_FILE \
|
||||
$TELEGRAM_API
|
||||
|
||||
curl -F chat_id=$TELEGRAM_CHAT_ID2 \
|
||||
-F document=@/backups/$ZIP_FILE \
|
||||
$TELEGRAM_API
|
||||
|
||||
ls -t /backups/*.zip | tail -n +4 | xargs rm -f
|
||||
ls -t /backups/*.sql | tail -n +4 | xargs rm -f
|
||||
echo "backup completed at $(date)" >> /var/log/cron/cron.log
|
||||
@@ -0,0 +1 @@
|
||||
0 3 * * * /app/backup.sh >> /var/log/cron/cron.log 2>&1
|
||||
@@ -0,0 +1,15 @@
|
||||
FROM alpine:3.19
|
||||
|
||||
RUN apk add --no-cache postgresql-client curl zip tzdata
|
||||
RUN ln -sf /usr/share/zoneinfo/UTC /etc/localtime
|
||||
RUN mkdir -p /var/log/cron /backups
|
||||
|
||||
WORKDIR /app
|
||||
COPY backup.sh .
|
||||
COPY crontab /etc/crontabs/root
|
||||
|
||||
RUN chmod +x backup.sh && \
|
||||
chmod 0644 /etc/crontabs/root && \
|
||||
touch /var/log/cron/cron.log
|
||||
|
||||
CMD ["busybox", "crond", "-f", "-L", "/var/log/cron/cron.log"]
|
||||
+19
-2
@@ -42,11 +42,28 @@ services:
|
||||
networks:
|
||||
- default
|
||||
|
||||
|
||||
db-backup:
|
||||
build:
|
||||
context: ./backup
|
||||
depends_on:
|
||||
- db
|
||||
environment:
|
||||
- PG_HOST=db
|
||||
- PG_PORT=5432
|
||||
- PG_DATABASE=hshop
|
||||
- PG_USER=byeto
|
||||
- PG_PASSWORD=vuhbyq-cypMu0-sirbon
|
||||
- TELEGRAM_BOT_TOKEN=7068288679:AAGecMnyt9A6R78OQu8nQeISMK1LepX718g
|
||||
- TELEGRAM_CHAT_ID=1198382521
|
||||
- TELEGRAM_CHAT_ID2=5115366609
|
||||
volumes:
|
||||
- backups:/backups
|
||||
networks:
|
||||
- default
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
media_data:
|
||||
|
||||
backups:
|
||||
networks:
|
||||
default:
|
||||
@@ -3,18 +3,21 @@
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
selectable?: boolean,
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
// props
|
||||
|
||||
defineProps<Props>();
|
||||
withDefaults(defineProps<Props>(), {
|
||||
selectable: false
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="size-[25px] rounded-full transition-all ring-2 ring-offset-4 shadow-black/30 shadow-inner"
|
||||
:class="selected ? 'ring-blue-500' : 'ring-transparent'"
|
||||
class="size-[25px] rounded-full transition-all shadow-black/30 shadow-inner"
|
||||
:class="[selectable ? 'ring-2 ring-offset-4 ' : '', selected ? 'ring-blue-500' : 'ring-transparent']"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,33 +1,54 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
// import
|
||||
|
||||
import type { LoadingOverlayProvideType } from "~/pages/index.vue";
|
||||
|
||||
// provide / inject
|
||||
|
||||
const { showLoadingOverlay } = inject("loadingOverlay") as LoadingOverlayProvideType;
|
||||
|
||||
// state
|
||||
|
||||
const { $gsap: gsap } = useNuxtApp();
|
||||
|
||||
const shouldRenderLoadingOverlay = ref(true);
|
||||
|
||||
// lifecycle
|
||||
|
||||
onMounted(() => {
|
||||
const timeline = gsap.timeline();
|
||||
watch(() => showLoadingOverlay.value, (value) => {
|
||||
if (!value) {
|
||||
const timeline = gsap.timeline();
|
||||
|
||||
timeline
|
||||
.to("#loading-overlay", {
|
||||
scale: 1
|
||||
})
|
||||
.to("#loading-overlay", {
|
||||
scale: 0.8,
|
||||
opacity: 0,
|
||||
delay: 5
|
||||
})
|
||||
.to("#loading-overlay", {
|
||||
opacity: 0,
|
||||
y: "20%"
|
||||
});
|
||||
timeline
|
||||
.to("#loading-overlay", {
|
||||
scale: 1
|
||||
})
|
||||
.to("#loading-overlay", {
|
||||
scale: 0.8,
|
||||
opacity: 0,
|
||||
delay: 3
|
||||
})
|
||||
.to("#loading-overlay", {
|
||||
opacity: 0,
|
||||
y: "20%",
|
||||
onComplete: () => {
|
||||
shouldRenderLoadingOverlay.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
once: true
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="loading-overlay" class="fixed inset-0 size-full z-9999 flex-center bg-black">
|
||||
<div
|
||||
v-if="shouldRenderLoadingOverlay"
|
||||
id="loading-overlay"
|
||||
class="fixed inset-0 size-full z-9999 flex-center bg-black"
|
||||
>
|
||||
<img id="loading-overlay-image" src="/video/loading-2.gif" class="opacity-0 scale-70 absolute z-20" alt="" />
|
||||
<div
|
||||
id="loading-overlay-gradient"
|
||||
@@ -41,7 +62,7 @@ onMounted(() => {
|
||||
#loading-overlay-image {
|
||||
animation-name: loading-overlay-image-animation;
|
||||
animation-duration: 1s;
|
||||
animation-delay: 0.75s;
|
||||
animation-delay: 0.35s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ const onSwiper = (swiper: SwiperClass) => {
|
||||
brand="برند محصول"
|
||||
:title="product.name"
|
||||
:picture="product.variants[0].images[0].image"
|
||||
:colors="product.variants.map(v => v.color)"
|
||||
:colors="product.colors"
|
||||
:price="product.variants[0].price"
|
||||
:rate="product.rating"
|
||||
:dark-layer="true"
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
import { Swiper, SwiperSlide } from "swiper/vue";
|
||||
import type { SwiperClass } from "swiper/react";
|
||||
import useHomeData from "~/composables/api/home/useHomeData";
|
||||
import type { LoadingOverlayProvideType } from "~/pages/index.vue";
|
||||
|
||||
// provide / inject
|
||||
|
||||
const { changeLoadingOverlay } = inject("loadingOverlay") as LoadingOverlayProvideType;
|
||||
|
||||
// state
|
||||
|
||||
@@ -31,6 +36,7 @@ let gsapTimeline: gsap.core.Timeline;
|
||||
// methods
|
||||
|
||||
const onSwiper = (swiper: SwiperClass) => {
|
||||
changeLoadingOverlay(false);
|
||||
swiper_instance.value = swiper;
|
||||
};
|
||||
|
||||
@@ -105,7 +111,7 @@ onMounted(() => {
|
||||
padding: "0px 80px"
|
||||
}, {
|
||||
padding: "0px 40px"
|
||||
}, "=")
|
||||
}, "=");
|
||||
|
||||
ScrollTrigger.create({
|
||||
trigger: "#header-slider-container",
|
||||
|
||||
@@ -101,6 +101,7 @@ watch(() => selectedVariant.value, (newValue) => {
|
||||
v-for="color in product!.colors"
|
||||
:key="color"
|
||||
@click="selectedColor = color"
|
||||
selectable
|
||||
:selected="selectedColor === color "
|
||||
:style="{backgroundColor: color}"
|
||||
class="cursor-pointer"
|
||||
|
||||
@@ -4,10 +4,32 @@
|
||||
|
||||
import useHomeData from "~/composables/api/home/useHomeData";
|
||||
|
||||
// type
|
||||
|
||||
export type LoadingOverlayProvideType = {
|
||||
showLoadingOverlay: Ref<boolean>,
|
||||
changeLoadingOverlay: (value: boolean) => void,
|
||||
}
|
||||
|
||||
// state
|
||||
|
||||
const { suspense } = useHomeData();
|
||||
|
||||
const showLoadingOverlay = ref(true);
|
||||
|
||||
// method
|
||||
|
||||
const changeLoadingOverlay = (value: boolean) => {
|
||||
showLoadingOverlay.value = value;
|
||||
};
|
||||
|
||||
// provide / inject
|
||||
|
||||
provide("loadingOverlay", {
|
||||
showLoadingOverlay,
|
||||
changeLoadingOverlay
|
||||
});
|
||||
|
||||
// ssr
|
||||
|
||||
const response = await suspense();
|
||||
@@ -23,7 +45,7 @@ if (response.isError) {
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<!-- <LoadingOverlay />-->
|
||||
<LoadingOverlay />
|
||||
<Hero />
|
||||
<Preview />
|
||||
<ProductsShowcase />
|
||||
|
||||
Reference in New Issue
Block a user