Merge branch 'main' of https://github.com/Byeto-Company/hossein_por_shop
@@ -4,4 +4,6 @@ from django.apps import AppConfig
|
|||||||
class AccountConfig(AppConfig):
|
class AccountConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'account'
|
name = 'account'
|
||||||
verbose_name = 'اکانت'
|
verbose_name = 'اکانت'
|
||||||
|
def ready(self):
|
||||||
|
import account.signals
|
||||||
@@ -129,7 +129,7 @@ class UserAddressModel(models.Model):
|
|||||||
city = models.CharField(max_length=30, verbose_name='شهر')
|
city = models.CharField(max_length=30, verbose_name='شهر')
|
||||||
province = models.CharField(max_length=30, verbose_name='استان')
|
province = models.CharField(max_length=30, verbose_name='استان')
|
||||||
for_me = models.BooleanField(default=False, verbose_name='برای خود کاربر')
|
for_me = models.BooleanField(default=False, verbose_name='برای خود کاربر')
|
||||||
is_main = models.BooleanField(default=False)
|
is_main = models.BooleanField(default=False, verbose_name='ادرس اصلی کاربر')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.user.phone}, {self.name}"
|
return f"{self.user.phone}, {self.name}"
|
||||||
@@ -152,6 +152,10 @@ class PushSubscription(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.user} push'
|
return f'{self.user} push'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'اشتراک نوتیفیکیشن'
|
||||||
|
verbose_name_plural = 'اشتراک های نوتیفیکیشن'
|
||||||
|
|
||||||
def send_notif(self, title, body, icon):
|
def send_notif(self, title, body, icon):
|
||||||
payload = {
|
payload = {
|
||||||
"title": 'فروشگاه هی ملز',
|
"title": 'فروشگاه هی ملز',
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from django.db import transaction
|
||||||
|
from django.db.models.signals import post_save
|
||||||
|
from django.dispatch import receiver
|
||||||
|
from .models import UserAddressModel
|
||||||
|
|
||||||
|
@receiver(post_save, sender=UserAddressModel)
|
||||||
|
def ensure_single_main_address(sender, instance, **kwargs):
|
||||||
|
if instance.is_main:
|
||||||
|
with transaction.atomic():
|
||||||
|
UserAddressModel.objects.filter(user=instance.user).exclude(pk=instance.pk).update(is_main=False)
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
from djoser.urls.jwt import views as djoser_jwt_views
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('profile', views.ProfileView.as_view()),
|
path('profile', views.ProfileView.as_view()),
|
||||||
path('verify', djoser_jwt_views.TokenVerifyView.as_view(), name='jwt-verify'),
|
path('verify', views.TokenVerifyView.as_view(), name='jwt-verify'),
|
||||||
path('send_otp', views.SendOTPView.as_view(), name='send-otp-view'),
|
path('send_otp', views.SendOTPView.as_view(), name='send-otp-view'),
|
||||||
path('yee_token_bedeeee', views.KonGhoshadToken.as_view()),
|
path('yee_token_bedeeee', views.KonGhoshadToken.as_view()),
|
||||||
path('address/create', views.CreateAddressView.as_view(), name='create-address'),
|
path('address/create', views.CreateAddressView.as_view(), name='create-address'),
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ from rest_framework.response import Response
|
|||||||
from .serializers import *
|
from .serializers import *
|
||||||
from .models import UserAddressModel, User, SecurityBreachAttemptModel
|
from .models import UserAddressModel, User, SecurityBreachAttemptModel
|
||||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||||
from drf_spectacular.utils import extend_schema, OpenApiParameter
|
from drf_spectacular.utils import extend_schema, OpenApiParameter, extend_schema_view
|
||||||
from rest_framework_simplejwt.views import TokenObtainPairView
|
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from rest_framework_simplejwt.tokens import RefreshToken
|
from rest_framework_simplejwt.tokens import RefreshToken
|
||||||
import ghasedak_sms
|
import ghasedak_sms
|
||||||
@@ -19,7 +19,7 @@ from rest_framework_simplejwt.tokens import RefreshToken
|
|||||||
class SendOTPView(APIView):
|
class SendOTPView(APIView):
|
||||||
permission_classes = [AllowAny]
|
permission_classes = [AllowAny]
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
tags=["Authentication"],
|
tags=["authentication"],
|
||||||
request={
|
request={
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -65,12 +65,18 @@ Code: {otp}"""
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return Response({'detail': f'error: {e} مشتی فعلا برو تو غمت نباشه تا بعدا یه کاریش بکنم', 'otp_code': otp}, 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)
|
# return Response({'detail': f'An error occurred: {e}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
@extend_schema_view(
|
||||||
|
post=extend_schema(tags=['authentication'])
|
||||||
|
)
|
||||||
|
class TokenRefreshView(TokenRefreshView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CustomTokenObtainPairView(TokenObtainPairView):
|
class CustomTokenObtainPairView(TokenObtainPairView):
|
||||||
serializer_class = CustomTokenObtainPairSerializer
|
serializer_class = CustomTokenObtainPairSerializer
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
tags=["Authentication"]
|
tags=["authentication"]
|
||||||
)
|
)
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
phone = request.data.get("phone")
|
phone = request.data.get("phone")
|
||||||
@@ -93,10 +99,10 @@ class CustomTokenObtainPairView(TokenObtainPairView):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
class KonGhoshadToken(TokenObtainPairView):
|
class KonGhoshadToken(APIView):
|
||||||
serializer_class = CustomTokenObtainPairSerializer
|
serializer_class = CustomTokenObtainPairSerializer
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
tags=["Authentication"]
|
tags=["authentication"]
|
||||||
)
|
)
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
random_user = User.objects.all().first()
|
random_user = User.objects.all().first()
|
||||||
@@ -116,12 +122,16 @@ class KonGhoshadToken(TokenObtainPairView):
|
|||||||
class ProfileView(APIView):
|
class ProfileView(APIView):
|
||||||
serializer_class = ProfileSerializer
|
serializer_class = ProfileSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
@extend_schema(
|
||||||
|
tags=["accounts profile"]
|
||||||
|
)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
user_ser = self.serializer_class(instance=request.user, context={'request': request})
|
user_ser = self.serializer_class(instance=request.user, context={'request': request})
|
||||||
return Response(user_ser.data, status=status.HTTP_200_OK)
|
return Response(user_ser.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
tags=["accounts profile"]
|
||||||
|
)
|
||||||
def patch(self, request):
|
def patch(self, request):
|
||||||
user = request.user
|
user = request.user
|
||||||
user_ser = self.serializer_class(user, data=request.data, partial=True, context={'request': request})
|
user_ser = self.serializer_class(user, data=request.data, partial=True, context={'request': request})
|
||||||
@@ -130,6 +140,10 @@ class ProfileView(APIView):
|
|||||||
return Response(user_ser.data)
|
return Response(user_ser.data)
|
||||||
return Response(user_ser.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(user_ser.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_view(
|
||||||
|
post=extend_schema(tags=["accounts address"])
|
||||||
|
)
|
||||||
class CreateAddressView(generics.CreateAPIView):
|
class CreateAddressView(generics.CreateAPIView):
|
||||||
queryset = UserAddressModel.objects.all()
|
queryset = UserAddressModel.objects.all()
|
||||||
serializer_class = UserAddressSerializer
|
serializer_class = UserAddressSerializer
|
||||||
@@ -139,7 +153,10 @@ class CreateAddressView(generics.CreateAPIView):
|
|||||||
user = self.request.user
|
user = self.request.user
|
||||||
is_first_address = not UserAddressModel.objects.filter(user=user).exists()
|
is_first_address = not UserAddressModel.objects.filter(user=user).exists()
|
||||||
serializer.save(user=user, is_main=is_first_address)
|
serializer.save(user=user, is_main=is_first_address)
|
||||||
|
@extend_schema_view(
|
||||||
|
put=extend_schema(tags=["accounts address"]),
|
||||||
|
patch=extend_schema(tags=["accounts address"])
|
||||||
|
)
|
||||||
class EditAddressView(generics.UpdateAPIView):
|
class EditAddressView(generics.UpdateAPIView):
|
||||||
queryset = UserAddressModel.objects.all()
|
queryset = UserAddressModel.objects.all()
|
||||||
serializer_class = UserAddressSerializer
|
serializer_class = UserAddressSerializer
|
||||||
@@ -147,21 +164,27 @@ class EditAddressView(generics.UpdateAPIView):
|
|||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return UserAddressModel.objects.filter(user=self.request.user)
|
return UserAddressModel.objects.filter(user=self.request.user)
|
||||||
|
@extend_schema_view(
|
||||||
|
delete=extend_schema(tags=["accounts address"])
|
||||||
|
)
|
||||||
class DeleteAddressView(generics.DestroyAPIView):
|
class DeleteAddressView(generics.DestroyAPIView):
|
||||||
queryset = UserAddressModel.objects.all()
|
queryset = UserAddressModel.objects.all()
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return UserAddressModel.objects.filter(user=self.request.user)
|
return UserAddressModel.objects.filter(user=self.request.user)
|
||||||
|
@extend_schema_view(
|
||||||
|
get=extend_schema(tags=["accounts address"])
|
||||||
|
)
|
||||||
class GetUserAddressesView(generics.ListAPIView):
|
class GetUserAddressesView(generics.ListAPIView):
|
||||||
serializer_class = UserAddressSerializer
|
serializer_class = UserAddressSerializer
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return UserAddressModel.objects.filter(user=self.request.user)
|
return UserAddressModel.objects.filter(user=self.request.user)
|
||||||
|
@extend_schema_view(
|
||||||
|
get=extend_schema(tags=["accounts address"])
|
||||||
|
)
|
||||||
class GetIDUserAddressView(generics.RetrieveAPIView):
|
class GetIDUserAddressView(generics.RetrieveAPIView):
|
||||||
serializer_class = UserAddressSerializer
|
serializer_class = UserAddressSerializer
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
@@ -174,6 +197,9 @@ class GetIDUserAddressView(generics.RetrieveAPIView):
|
|||||||
class SubscribeView(APIView):
|
class SubscribeView(APIView):
|
||||||
serializer_class = PushSubscriptionSerializer
|
serializer_class = PushSubscriptionSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
@extend_schema(
|
||||||
|
tags=["accounts subscribe"]
|
||||||
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
push_ser = self.serializer_class(data=request.data)
|
push_ser = self.serializer_class(data=request.data)
|
||||||
if push_ser.is_valid():
|
if push_ser.is_valid():
|
||||||
@@ -191,6 +217,9 @@ class UnsubscribeSerializer(serializers.Serializer):
|
|||||||
class UnsubscribeView(APIView):
|
class UnsubscribeView(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
serializer_class = UnsubscribeSerializer
|
serializer_class = UnsubscribeSerializer
|
||||||
|
@extend_schema(
|
||||||
|
tags=["accounts subscribe"]
|
||||||
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
endpoint = request.data.get("end_point")
|
endpoint = request.data.get("end_point")
|
||||||
if not endpoint:
|
if not endpoint:
|
||||||
@@ -221,6 +250,7 @@ class LogoutView(APIView):
|
|||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
request=LogoutSerializer,
|
request=LogoutSerializer,
|
||||||
|
tags=["authentication"],
|
||||||
responses={205: None, 400: "Bad request (invalid token or missing data)"},
|
responses={205: None, 400: "Bad request (invalid token or missing data)"},
|
||||||
)
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
@@ -230,4 +260,12 @@ class LogoutView(APIView):
|
|||||||
token.blacklist()
|
token.blacklist()
|
||||||
return Response(status=status.HTTP_205_RESET_CONTENT)
|
return Response(status=status.HTTP_205_RESET_CONTENT)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
from djoser.urls.jwt import views as djoser_jwt_views
|
||||||
|
@extend_schema_view(
|
||||||
|
post=extend_schema(tags=["authentication"])
|
||||||
|
)
|
||||||
|
class TokenVerifyView(djoser_jwt_views.TokenVerifyView):
|
||||||
|
pass
|
||||||
@@ -66,6 +66,7 @@ class BankAdmin(ModelAdmin):
|
|||||||
"extra_information",
|
"extra_information",
|
||||||
"created_at",
|
"created_at",
|
||||||
"update_at",
|
"update_at",
|
||||||
|
'order',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class Bank(models.Model):
|
|||||||
max_length=255, blank=True, null=True, verbose_name=_("Bank choose identifier")
|
max_length=255, blank=True, null=True, verbose_name=_("Bank choose identifier")
|
||||||
)
|
)
|
||||||
|
|
||||||
order = models.ForeignKey(OrderModel, on_delete=models.SET_NULL, null=True, blank=True ,related_name='bank_records')
|
order = models.ForeignKey(OrderModel, on_delete=models.SET_NULL, null=True, blank=True ,related_name='bank_records', verbose_name='سفارش')
|
||||||
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True, editable=False, verbose_name=_("Created at"))
|
created_at = models.DateTimeField(auto_now_add=True, editable=False, verbose_name=_("Created at"))
|
||||||
update_at = models.DateTimeField(auto_now=True, editable=False, verbose_name=_("Updated at"))
|
update_at = models.DateTimeField(auto_now=True, editable=False, verbose_name=_("Updated at"))
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ from django.contrib import admin
|
|||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from drf_spectacular.views import SpectacularSwaggerView, SpectacularAPIView
|
from drf_spectacular.views import SpectacularSwaggerView, SpectacularAPIView
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from rest_framework_simplejwt.views import TokenObtainPairView,TokenRefreshView
|
from rest_framework_simplejwt.views import TokenObtainPairView
|
||||||
from product import views
|
from product import views
|
||||||
from account.views import CustomTokenObtainPairView
|
from account.views import CustomTokenObtainPairView, TokenRefreshView
|
||||||
from home.views import HomeView
|
from home.views import HomeView
|
||||||
from .views import FakeAdminLoginView
|
from .views import FakeAdminLoginView
|
||||||
from azbankgateways.urls import az_bank_gateways_urls
|
from azbankgateways.urls import az_bank_gateways_urls
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class BankRecordInline(StackedInline):
|
|||||||
model = Bank
|
model = Bank
|
||||||
extra = 0
|
extra = 0
|
||||||
max_num = 0
|
max_num = 0
|
||||||
|
tab = True
|
||||||
def has_delete_permission(self, request, obj=None):
|
def has_delete_permission(self, request, obj=None):
|
||||||
return False
|
return False
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
@@ -46,11 +47,11 @@ class BankRecordInline(StackedInline):
|
|||||||
class OrderAdmin(ModelAdmin, ImportExportModelAdmin):
|
class OrderAdmin(ModelAdmin, ImportExportModelAdmin):
|
||||||
import_form_class = ImportForm
|
import_form_class = ImportForm
|
||||||
export_form_class = ExportForm
|
export_form_class = ExportForm
|
||||||
|
search_fields = ['order_id', 'user__phone', 'user__first_name', 'user__last_name', 'user__email']
|
||||||
list_filter = ['is_paid', 'status']
|
list_filter = ['is_paid', 'status']
|
||||||
actions_list = ['redirect_to_learn', 'udpate_bank_status']
|
actions_list = ['redirect_to_learn', 'udpate_bank_status']
|
||||||
list_display = ['user', 'is_paid', 'status', 'discount_code', 'address',]
|
list_display = ['order_id', 'user', 'is_paid', 'status', 'discount_code', 'address',]
|
||||||
readonly_fields = ('created_at', )
|
readonly_fields = ('created_at', 'order_id', 'tax', 'final_price', 'cart_total', 'discount', 'discount_code', 'user', 'address', 'is_paid')
|
||||||
compressed_fields = True
|
compressed_fields = True
|
||||||
warn_unsaved_form = True
|
warn_unsaved_form = True
|
||||||
# exclude = ('bank_records',)
|
# exclude = ('bank_records',)
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-03-27 10:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('order', '0023_remove_ordermodel_bank_records'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='orderitemmodel',
|
||||||
|
name='discount',
|
||||||
|
field=models.SmallIntegerField(default=0, verbose_name='تخفیف'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ordermodel',
|
||||||
|
name='order_id',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -44,6 +44,7 @@ class OrderModel(models.Model):
|
|||||||
('CANCELED', 'لغو شده'),
|
('CANCELED', 'لغو شده'),
|
||||||
('REFUNDED', 'مرجوع شده'),
|
('REFUNDED', 'مرجوع شده'),
|
||||||
]
|
]
|
||||||
|
order_id = models.PositiveIntegerField(unique=True, null=True, blank=True, verbose_name='شماره سفارش')
|
||||||
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='orders', verbose_name='کاربر')
|
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='orders', verbose_name='کاربر')
|
||||||
address = models.ForeignKey(UserAddressModel, on_delete=models.SET_NULL, related_name='orders', null=True, verbose_name='ادرس')
|
address = models.ForeignKey(UserAddressModel, on_delete=models.SET_NULL, related_name='orders', null=True, verbose_name='ادرس')
|
||||||
created_at = jmodels.jDateField(blank=True, null=True, verbose_name="تاریخ ثبت سفارش")
|
created_at = jmodels.jDateField(blank=True, null=True, verbose_name="تاریخ ثبت سفارش")
|
||||||
@@ -54,7 +55,6 @@ class OrderModel(models.Model):
|
|||||||
tax = models.BigIntegerField(null=True, blank=True, verbose_name='مالیات')
|
tax = models.BigIntegerField(null=True, blank=True, verbose_name='مالیات')
|
||||||
final_price = models.BigIntegerField(null=True, blank=True, verbose_name='قیمت نهایی')
|
final_price = models.BigIntegerField(null=True, blank=True, verbose_name='قیمت نهایی')
|
||||||
cart_total = models.BigIntegerField(null=True, blank=True, verbose_name='کل سبد خرید')
|
cart_total = models.BigIntegerField(null=True, blank=True, verbose_name='کل سبد خرید')
|
||||||
# bank_records = models.ManyToManyField(Bank, max_length=100, verbose_name='رکورد بانکی', null=True, blank=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'سفارش: {self.id + 1000}'
|
return f'سفارش: {self.id + 1000}'
|
||||||
@@ -66,39 +66,32 @@ class OrderModel(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
try:
|
if not self.pk:
|
||||||
push_object = PushSubscription.objects.get(user=self.user)
|
last_instance = self.__class__.objects.order_by("pk").last()
|
||||||
except:
|
self.order_id = (last_instance.pk + 1001) if last_instance else 1001
|
||||||
print('object not found')
|
|
||||||
try:
|
|
||||||
push_object.send_notif(f'سفارش شما به {self.get_status_display()} تغییر کرد', f'سفارش شما به {self.get_status_display()} تغییر کرد', ProductImageModel.objects.all().first().image.url)
|
|
||||||
except:
|
|
||||||
print('didnt send')
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# def discount(self):
|
def cal_discount(self):
|
||||||
# # total_with_item_discount = sum(item.total_with_discount() for item in self.items.all())
|
# total_with_item_discount = sum(item.total_with_discount() for item in self.items.all())
|
||||||
# # discount_percent = self.discount_code.percent
|
# discount_percent = self.discount_code.percent
|
||||||
# # return total_with_item_discount * ((100 - discount_percent) / 100)
|
# return total_with_item_discount * ((100 - discount_percent) / 100)
|
||||||
# pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# def tax(self):
|
def cal_tax(self):
|
||||||
# return self.total_without_tax() * 0.2
|
return self.total_without_tax() * 0.2
|
||||||
|
|
||||||
|
|
||||||
# def total(self):
|
def cal_total(self):
|
||||||
# pass
|
pass
|
||||||
# return self.total_with_discount() + self.tax()
|
return self.total_with_discount() + self.tax()
|
||||||
|
|
||||||
|
|
||||||
# def final_price(self):
|
def cal_final_price(self):
|
||||||
# pass
|
pass
|
||||||
|
|
||||||
# def submit_cart(self):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -108,15 +101,20 @@ class OrderItemModel(models.Model):
|
|||||||
quantity = models.PositiveSmallIntegerField(verbose_name="تعداد")
|
quantity = models.PositiveSmallIntegerField(verbose_name="تعداد")
|
||||||
price = models.PositiveIntegerField(verbose_name='قیمت', default=0)
|
price = models.PositiveIntegerField(verbose_name='قیمت', default=0)
|
||||||
product = models.ForeignKey(ProductVariant, on_delete=models.PROTECT, verbose_name="محصول")
|
product = models.ForeignKey(ProductVariant, on_delete=models.PROTECT, verbose_name="محصول")
|
||||||
|
discount = models.SmallIntegerField(default=0, verbose_name='تخفیف')
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'ایتم سبد خرید'
|
verbose_name = 'ایتم سبد خرید'
|
||||||
verbose_name_plural = 'ایتم های سبد خرید'
|
verbose_name_plural = 'ایتم های سبد خرید'
|
||||||
|
|
||||||
def total(self):
|
# def total(self):
|
||||||
return self.quantity * self.product.price
|
# return self.quantity * self.product.price
|
||||||
|
|
||||||
|
# def total_with_discount(self):
|
||||||
|
# return self.quantity * self.product.get_toman_price_after_discount()
|
||||||
|
|
||||||
|
def update_fields(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def total_with_discount(self):
|
|
||||||
return self.quantity * self.product.get_toman_price_after_discount()
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'({self.product}) - ({self.order.user})'
|
return f'({self.product}) - ({self.order.user})'
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ class CartSerializer(serializers.ModelSerializer):
|
|||||||
class OrderListSerializer(serializers.ModelSerializer):
|
class OrderListSerializer(serializers.ModelSerializer):
|
||||||
count = serializers.SerializerMethodField()
|
count = serializers.SerializerMethodField()
|
||||||
images = serializers.SerializerMethodField()
|
images = serializers.SerializerMethodField()
|
||||||
order_id = serializers.SerializerMethodField()
|
|
||||||
verbose_status = serializers.SerializerMethodField()
|
verbose_status = serializers.SerializerMethodField()
|
||||||
class Meta:
|
class Meta:
|
||||||
model = OrderModel
|
model = OrderModel
|
||||||
@@ -127,8 +126,6 @@ class OrderListSerializer(serializers.ModelSerializer):
|
|||||||
for item in obj.items.all()[:3]
|
for item in obj.items.all()[:3]
|
||||||
]
|
]
|
||||||
return filter(lambda x: x is not None, image_list)
|
return filter(lambda x: x is not None, image_list)
|
||||||
def get_order_id(self, obj):
|
|
||||||
return obj.id + 1000
|
|
||||||
|
|
||||||
|
|
||||||
class OrderGetSerializer(serializers.ModelSerializer):
|
class OrderGetSerializer(serializers.ModelSerializer):
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from .views import CartItemViews, CartView, OrderlistView, CartItemClear, ApplyDiscountView, OrderGetView, SetAddressForCartView
|
from .views import *
|
||||||
from .views import PaymentView, callback_view
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('all', OrderlistView.as_view(), name='order-list'),
|
path('all', OrderlistView.as_view(), name='order-list'),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from .models import OrderItemModel, OrderModel, DiscountCode
|
|||||||
from .permissons import CanDeleteCartItemPermissions, GetOrderPermission, SetAddressPermissions
|
from .permissons import CanDeleteCartItemPermissions, GetOrderPermission, SetAddressPermissions
|
||||||
from azbankgateways import bankfactories, models as bank_models
|
from azbankgateways import bankfactories, models as bank_models
|
||||||
from azbankgateways.exceptions import AZBankGatewaysException
|
from azbankgateways.exceptions import AZBankGatewaysException
|
||||||
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes
|
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes, extend_schema_view
|
||||||
from utils.pagination import StructurePagination
|
from utils.pagination import StructurePagination
|
||||||
from order.models import OrderModel
|
from order.models import OrderModel
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -23,7 +23,10 @@ from account.models import UserAddressModel
|
|||||||
# pass
|
# pass
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_view(
|
||||||
|
post=extend_schema(tags=["cart discount code"]),
|
||||||
|
delete=extend_schema(tags=["cart discount code"]),
|
||||||
|
)
|
||||||
class ApplyDiscountView(APIView):
|
class ApplyDiscountView(APIView):
|
||||||
serializer_class = DiscountCodeSerializer
|
serializer_class = DiscountCodeSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
@@ -52,6 +55,9 @@ class ApplyDiscountView(APIView):
|
|||||||
class CartItemClear(APIView):
|
class CartItemClear(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
serializer_class = OrderItemSerailzier
|
serializer_class = OrderItemSerailzier
|
||||||
|
@extend_schema(
|
||||||
|
tags=["order cart"]
|
||||||
|
)
|
||||||
def delete(self, request):
|
def delete(self, request):
|
||||||
cart_order, created = OrderModel.objects.get_or_create(
|
cart_order, created = OrderModel.objects.get_or_create(
|
||||||
user=request.user,
|
user=request.user,
|
||||||
@@ -60,7 +66,10 @@ class CartItemClear(APIView):
|
|||||||
cart_order.items.all().delete()
|
cart_order.items.all().delete()
|
||||||
return Response({'detail': f'سبد خرید با موفقیت خالی شد'}, status=status.HTTP_204_NO_CONTENT)
|
return Response({'detail': f'سبد خرید با موفقیت خالی شد'}, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
@extend_schema_view(
|
||||||
|
post=extend_schema(tags=["order cart"]),
|
||||||
|
delete=extend_schema(tags=["order cart"]),
|
||||||
|
)
|
||||||
class CartItemViews(APIView):
|
class CartItemViews(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
serializer_class = OrderItemSerailzier
|
serializer_class = OrderItemSerailzier
|
||||||
@@ -101,6 +110,9 @@ class CartItemViews(APIView):
|
|||||||
class CartView(APIView):
|
class CartView(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
serializer_class = CartSerializer
|
serializer_class = CartSerializer
|
||||||
|
@extend_schema(
|
||||||
|
tags=["order cart"]
|
||||||
|
)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
user = request.user
|
user = request.user
|
||||||
cart_instance, created = OrderModel.objects.get_or_create(user=user, status='CART')
|
cart_instance, created = OrderModel.objects.get_or_create(user=user, status='CART')
|
||||||
@@ -144,7 +156,8 @@ class OrderlistView(APIView):
|
|||||||
required=False,
|
required=False,
|
||||||
type=OpenApiTypes.STR,
|
type=OpenApiTypes.STR,
|
||||||
),
|
),
|
||||||
]
|
],
|
||||||
|
tags=["order"]
|
||||||
)
|
)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
user = request.user
|
user = request.user
|
||||||
@@ -191,7 +204,8 @@ class PaymentView(APIView):
|
|||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
serializer_class = BankTypeSerializer
|
serializer_class = BankTypeSerializer
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
description="choices=['BMI', 'SEP', 'ZARINPAL', 'IDPAY', 'ZIBAL', 'BAHAMTA', 'MELLAT', 'PAYV1']"
|
description="choices=['BMI', 'SEP', 'ZARINPAL', 'IDPAY', 'ZIBAL', 'BAHAMTA', 'MELLAT', 'PAYV1']",
|
||||||
|
tags=['order payment']
|
||||||
)
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
print(request.data.get('gateway_type'))
|
print(request.data.get('gateway_type'))
|
||||||
@@ -261,6 +275,9 @@ class SetAddressSerilizer(serializers.Serializer):
|
|||||||
class SetAddressForCartView(APIView):
|
class SetAddressForCartView(APIView):
|
||||||
serializer_class = SetAddressSerilizer
|
serializer_class = SetAddressSerilizer
|
||||||
permission_classes = [IsAuthenticated, SetAddressPermissions]
|
permission_classes = [IsAuthenticated, SetAddressPermissions]
|
||||||
|
@extend_schema(
|
||||||
|
tags=["order cart"]
|
||||||
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
address_id = request.data.get('address_id', None)
|
address_id = request.data.get('address_id', None)
|
||||||
if not address_id:
|
if not address_id:
|
||||||
|
|||||||
@@ -178,16 +178,22 @@ class CommentModel(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class AttributeType(models.Model):
|
class AttributeType(models.Model):
|
||||||
name = models.CharField(verbose_name='نام نوع اتربیوت', max_length=100)
|
name = models.CharField(verbose_name='نام نوع متغییر', max_length=100)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'نوع متغییر محصول'
|
||||||
|
verbose_name_plural = 'نوع های متغییر محصول'
|
||||||
|
|
||||||
class AttributeValue(models.Model):
|
class AttributeValue(models.Model):
|
||||||
attribute_type = models.ForeignKey(AttributeType, on_delete=models.CASCADE, blank=True, null=True)
|
attribute_type = models.ForeignKey(AttributeType, on_delete=models.CASCADE, blank=True, null=True)
|
||||||
value = models.CharField(verbose_name='مقدار نوع اتربیوت', max_length=100, blank=True, null=True)
|
value = models.CharField(verbose_name='مقدار نوع اتربیوت', max_length=100, blank=True, null=True)
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('attribute_type', 'value')
|
unique_together = ('attribute_type', 'value')
|
||||||
|
verbose_name = 'مقدار متغییر محصول'
|
||||||
|
verbose_name_plural = 'مقدار های متغییر محصول'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.attribute_type}: {self.value}"
|
return f"{self.attribute_type}: {self.value}"
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ class Attachment(models.Model):
|
|||||||
self.name = self.file.name
|
self.name = self.file.name
|
||||||
super(Attachment, self).save(*args, **kwargs)
|
super(Attachment, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'پیوست تیکت'
|
||||||
|
verbose_name_plural = 'پیوست های تیکت'
|
||||||
|
|
||||||
class Ticket(models.Model):
|
class Ticket(models.Model):
|
||||||
# objects = jmodels.jManager()
|
# objects = jmodels.jManager()
|
||||||
STATUS_CHOICES = [
|
STATUS_CHOICES = [
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ services:
|
|||||||
- django
|
- django
|
||||||
networks:
|
networks:
|
||||||
- default
|
- default
|
||||||
|
environment:
|
||||||
|
- API_BASE_URL="https://api.heymlz.com"
|
||||||
|
- DEBUG="false"
|
||||||
|
- NUXT_IMAGE_DOMAINS="https://c262408.parspack.net"
|
||||||
|
|
||||||
django:
|
django:
|
||||||
container_name: shop_backend
|
container_name: shop_backend
|
||||||
|
|||||||
@@ -35,6 +35,6 @@ const closeModal = () => {
|
|||||||
/>
|
/>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
|
|
||||||
<VueQueryDevtools dir="ltr" buttonPosition="bottom-right" />
|
<VueQueryDevtools dir="ltr" buttonPosition="top-right"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -44,6 +44,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@utility btn-primary {
|
||||||
|
@apply text-white bg-blue-500 border-[1.5px] border-transparent;
|
||||||
|
@apply btn-lg;
|
||||||
|
|
||||||
|
svg[class~="iconify"] path {
|
||||||
|
@apply stroke-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@apply bg-transparent border-blue-500 text-blue-500;
|
||||||
|
|
||||||
|
svg[class~="iconify"] path {
|
||||||
|
@apply stroke-blue-500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
@apply bg-slate-100 text-slate-400;
|
||||||
|
|
||||||
|
svg[class~="iconify"] path {
|
||||||
|
@apply stroke-slate-400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@utility btn-secondary {
|
@utility btn-secondary {
|
||||||
@apply text-black bg-slate-100;
|
@apply text-black bg-slate-100;
|
||||||
@apply btn-lg;
|
@apply btn-lg;
|
||||||
|
|||||||
@@ -12,42 +12,55 @@ const {} = toRefs(props);
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative w-full flex flex-col justify-center min-h-[450px] md:h-[80svh]">
|
<div class="relative w-full flex flex-col justify-center min-h-[450px] h-svh">
|
||||||
|
<div class="flex-col-center gap-6 mb-32">
|
||||||
|
<span class="typo-h-6 max-sm:text-xl md:typo-h-5 lg:typo-h-4 text-black">
|
||||||
|
مجله در ستون و سطرآنچ
|
||||||
|
</span>
|
||||||
|
<p class="text-slate-500 text-center max-w-[750px] typo-p-lg xl:typo-p-xl">
|
||||||
|
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. چاپگرها و
|
||||||
|
متون بلکه روزنامه و مجله در ستون و سطرآنچنان که
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div class="-rotate-z-2 z-20">
|
<div class="-rotate-z-2 z-20">
|
||||||
<div
|
<div
|
||||||
class="bg-warning-500 flex pr-20 gap-12 sm:gap-20 py-2 w-max animate-marquee-reverse"
|
class="bg-blue-500 flex items-center pr-20 gap-12 sm:gap-20 w-max animate-marquee-reverse h-[140px]"
|
||||||
>
|
>
|
||||||
<span
|
<template v-for="i in 10">
|
||||||
v-for="i in 10"
|
<div class="text-[30px] lg:text-[40px] text-white whitespace-nowrap font-semibold opacity-85">
|
||||||
class="text-[40px] lg:text-[50px] text-white whitespace-nowrap font-semibold"
|
HEYMLZ
|
||||||
>
|
</div>
|
||||||
TEST {{ i }}
|
<NuxtImg src="/img/heymlz/heymlz-logo.png" class="h-[45px] invert opacity-85" />
|
||||||
</span>
|
</template>
|
||||||
<span
|
<template v-for="i in 10">
|
||||||
v-for="i in 10"
|
<div class="text-[30px] lg:text-[40px] text-white whitespace-nowrap font-semibold opacity-85">
|
||||||
class="text-[40px] lg:text-[50px] text-white whitespace-nowrap font-semibold"
|
HEYMLZ
|
||||||
>
|
</div>
|
||||||
TEST {{ i }}
|
<NuxtImg src="/img/heymlz/heymlz-logo.png" class="h-[45px] invert opacity-85" />
|
||||||
</span>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rotate-z-2 z-10">
|
<div class="rotate-z-2 z-10">
|
||||||
<div
|
<div
|
||||||
class="bg-slate-50 flex pr-20 gap-12 sm:gap-20 py-2 w-max animate-marquee"
|
class="bg-slate-100/70 flex items-center pr-20 gap-12 sm:gap-20 w-max animate-marquee h-[140px]"
|
||||||
>
|
>
|
||||||
<span
|
<template v-for="i in 1">
|
||||||
v-for="i in 10"
|
<NuxtImg src="/img/brands/brand-1.png" class="h-[45px] grayscale opacity-40" />
|
||||||
class="text-[40px] lg:text-[50px] text-slate-300 whitespace-nowrap font-semibold"
|
<NuxtImg src="/img/brands/brand-2.png" class="h-[45px] grayscale opacity-40" />
|
||||||
>
|
<NuxtImg src="/img/brands/brand-3.png" class="h-[45px] grayscale opacity-40" />
|
||||||
TEST {{ i }}
|
<NuxtImg src="/img/brands/brand-4.png" class="h-[45px] grayscale opacity-40" />
|
||||||
</span>
|
<NuxtImg src="/img/brands/brand-5.png" class="h-[45px] grayscale opacity-40" />
|
||||||
<span
|
<NuxtImg src="/img/brands/brand-6.png" class="h-[45px] grayscale opacity-40" />
|
||||||
v-for="i in 10"
|
</template>
|
||||||
class="text-[40px] lg:text-[50px] text-slate-300 whitespace-nowrap font-semibold"
|
<template v-for="i in 1">
|
||||||
>
|
<NuxtImg src="/img/brands/brand-1.png" class="h-[45px] grayscale opacity-40" />
|
||||||
TEST {{ i }}
|
<NuxtImg src="/img/brands/brand-2.png" class="h-[45px] grayscale opacity-40" />
|
||||||
</span>
|
<NuxtImg src="/img/brands/brand-3.png" class="h-[45px] grayscale opacity-40" />
|
||||||
|
<NuxtImg src="/img/brands/brand-4.png" class="h-[45px] grayscale opacity-40" />
|
||||||
|
<NuxtImg src="/img/brands/brand-5.png" class="h-[45px] grayscale opacity-40" />
|
||||||
|
<NuxtImg src="/img/brands/brand-6.png" class="h-[45px] grayscale opacity-40" />
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// types
|
// types
|
||||||
type Props = {
|
type Props = {
|
||||||
variant?: "solid" | "secondary" | "outlined" | "ghost";
|
variant?: "solid" | "secondary" | "outlined" | "ghost" | "primary";
|
||||||
size?: "xl" | "lg" | "md";
|
size?: "xl" | "lg" | "md";
|
||||||
startIcon?: string;
|
startIcon?: string;
|
||||||
endIcon?: string;
|
endIcon?: string;
|
||||||
@@ -24,6 +24,7 @@ const classes = computed(() => {
|
|||||||
"btn-secondary": variant.value === "secondary",
|
"btn-secondary": variant.value === "secondary",
|
||||||
"btn-outlined": variant.value === "outlined",
|
"btn-outlined": variant.value === "outlined",
|
||||||
"btn-ghost": variant.value === "ghost",
|
"btn-ghost": variant.value === "ghost",
|
||||||
|
"btn-primary": variant.value === "primary",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"btn-xl": size.value === "xl",
|
"btn-xl": size.value === "xl",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ withDefaults(defineProps<Props>(), {
|
|||||||
</span>
|
</span>
|
||||||
<NuxtLink to="/products">
|
<NuxtLink to="/products">
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="primary"
|
||||||
class="rounded-full max-sm:typo-label-sm max-sm:py-2"
|
class="rounded-full max-sm:typo-label-sm max-sm:py-2"
|
||||||
end-icon="ci:arrow-left"
|
end-icon="ci:arrow-left"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -25,28 +25,27 @@ const onSwiper = (swiper: SwiperClass) => {
|
|||||||
class="flex flex-col justify-center gap-4 bg-black h-[150svh] relative overflow-hidden"
|
class="flex flex-col justify-center gap-4 bg-black h-[150svh] relative overflow-hidden"
|
||||||
>
|
>
|
||||||
|
|
||||||
<!-- <div class="w-full flex justify-center items-center relative z-10">-->
|
<div class="w-full relative translate-y-[-200px] z-10">
|
||||||
<!-- <span class="text-white typo-h-6 md:typo-h-5 lg:typo-h-4">-->
|
<div class="flex-col-center gap-6">
|
||||||
<!-- دسته بندی ها-->
|
<span class="text-white typo-h-6 md:typo-h-5 lg:typo-h-4">
|
||||||
<!-- </span>-->
|
دسته بندی ها
|
||||||
<!-- </div>-->
|
</span>
|
||||||
|
<p class="text-slate-300 text-center max-w-[750px] typo-p-lg xl:typo-p-xl">
|
||||||
|
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. چاپگرها
|
||||||
|
و
|
||||||
|
متون بلکه روزنامه و مجله در ستون و سطرآنچنان که
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<NuxtImg
|
|
||||||
src="/img/categories-gradient.png"
|
|
||||||
class="animate-spin [animation-duration:16s] object-cover absolute size-full brightness-45 scale-115 aspect-square"
|
|
||||||
:style="{
|
|
||||||
maskImage: 'radial-gradient(black, transparent 50%)'
|
|
||||||
}"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="w-full my-20 relative">
|
<div class="w-full my-20 relative">
|
||||||
<video
|
<video
|
||||||
class="aspect-square w-[450px] translate-[-253px] absolute left-1/2 -translate-x-1/2 z-10"
|
class="aspect-square w-[450px] translate-[-293px] absolute left-1/2 -translate-x-1/2 z-10"
|
||||||
:style="{
|
:style="{
|
||||||
filter: 'drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.4))',
|
filter: 'drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.4))',
|
||||||
}"
|
}"
|
||||||
src="/video/heymlz/heymlz-seat-2.webm"
|
src="/video/heymlz/heymlz-handshake-part-1.webm"
|
||||||
autoplay
|
autoplay
|
||||||
playsinline
|
playsinline
|
||||||
webkit-playsinline
|
webkit-playsinline
|
||||||
@@ -109,7 +108,7 @@ const onSwiper = (swiper: SwiperClass) => {
|
|||||||
|
|
||||||
<div class="w-full flex justify-center items-center">
|
<div class="w-full flex justify-center items-center">
|
||||||
<NuxtLink to="/category">
|
<NuxtLink to="/category">
|
||||||
<Button variant="solid" class="invert rounded-full max-xs:typo-label-sm !px-4 xs:!px-8"
|
<Button variant="primary" class="rounded-full max-xs:typo-label-sm !px-4 xs:!px-8"
|
||||||
end-icon="ci:arrow-left">
|
end-icon="ci:arrow-left">
|
||||||
مشاهده همه دسته ها
|
مشاهده همه دسته ها
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ onUnmounted(() => {
|
|||||||
:centered-slides="true"
|
:centered-slides="true"
|
||||||
:breakpoints="{
|
:breakpoints="{
|
||||||
768: {
|
768: {
|
||||||
spaceBetween : 40,
|
spaceBetween : 40
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@swiper="onSwiper"
|
@swiper="onSwiper"
|
||||||
@@ -234,7 +234,8 @@ onUnmounted(() => {
|
|||||||
</span>
|
</span>
|
||||||
<NuxtLink :to="slide.link">
|
<NuxtLink :to="slide.link">
|
||||||
<Button
|
<Button
|
||||||
class="max-sm:hidden max-lg:typo-label-xs invert rounded-full hover:bg-transparent"
|
variant="primary"
|
||||||
|
class="max-sm:hidden max-lg:typo-label-xs px-7 rounded-full hover:bg-transparent"
|
||||||
>
|
>
|
||||||
مشاهده
|
مشاهده
|
||||||
</Button>
|
</Button>
|
||||||
@@ -249,7 +250,7 @@ onUnmounted(() => {
|
|||||||
>
|
>
|
||||||
<button @click="swiper_instance?.slidePrev()">
|
<button @click="swiper_instance?.slidePrev()">
|
||||||
<Icon
|
<Icon
|
||||||
class="**:stroke-white cursor-pointer size-5 md:size-6"
|
class="**:stroke-white cursor-pointer size-6 md:size-8"
|
||||||
name="ci:arrow-right"
|
name="ci:arrow-right"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
@@ -264,7 +265,7 @@ onUnmounted(() => {
|
|||||||
<button>
|
<button>
|
||||||
<Icon
|
<Icon
|
||||||
@click="swiper_instance?.slideNext()"
|
@click="swiper_instance?.slideNext()"
|
||||||
class="**:stroke-white cursor-pointer size-5 md:size-6"
|
class="**:stroke-white cursor-pointer size-6 md:size-8"
|
||||||
name="ci:arrow-left"
|
name="ci:arrow-left"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ await suspense();
|
|||||||
مقالات اخیر سایت
|
مقالات اخیر سایت
|
||||||
</span>
|
</span>
|
||||||
<NuxtLink to="/articles">
|
<NuxtLink to="/articles">
|
||||||
<Button variant="outlined" class="rounded-full max-sm:typo-label-sm max-sm:py-2"
|
<Button variant="primary" class="rounded-full max-sm:typo-label-sm max-sm:py-2"
|
||||||
end-icon="ci:arrow-left">
|
end-icon="ci:arrow-left">
|
||||||
نمایش همه
|
نمایش همه
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -15,6 +15,13 @@ const activeSlideVideo = ref<"left" | "right" | "none">("none");
|
|||||||
const draggableEl = ref<HTMLElement | null>(null);
|
const draggableEl = ref<HTMLElement | null>(null);
|
||||||
const previewContainerEl = ref<HTMLElement | null>(null);
|
const previewContainerEl = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const heymlzElement = useTemplateRef<HTMLDivElement>("heymlzElement");
|
||||||
|
const heymlzElementIsVisible = useElementVisibility(heymlzElement, {
|
||||||
|
rootMargin: "0px 0px -40% 0px"
|
||||||
|
});
|
||||||
|
|
||||||
|
const showHeymlzAnimation = ref(false);
|
||||||
|
|
||||||
const { x: dragAxisX } = useDraggable(draggableEl, {
|
const { x: dragAxisX } = useDraggable(draggableEl, {
|
||||||
initialValue: { x: 0, y: 0 },
|
initialValue: { x: 0, y: 0 },
|
||||||
axis: "x"
|
axis: "x"
|
||||||
@@ -22,6 +29,17 @@ const { x: dragAxisX } = useDraggable(draggableEl, {
|
|||||||
|
|
||||||
// watch
|
// watch
|
||||||
|
|
||||||
|
watch(heymlzElementIsVisible, (newValue) => {
|
||||||
|
if (newValue) {
|
||||||
|
showHeymlzAnimation.value = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
showHeymlzAnimation.value = false;
|
||||||
|
}, 3200);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
once: true
|
||||||
|
});
|
||||||
|
|
||||||
watch(() => clipPathPercent.value, (newValue) => {
|
watch(() => clipPathPercent.value, (newValue) => {
|
||||||
if (newValue > 80) {
|
if (newValue > 80) {
|
||||||
activeSlideVideo.value = "right";
|
activeSlideVideo.value = "right";
|
||||||
@@ -51,18 +69,19 @@ watch(
|
|||||||
<div class="flex flex-col items-center gap-3 mb-10 lg:mb-16">
|
<div class="flex flex-col items-center gap-3 mb-10 lg:mb-16">
|
||||||
<span class="typo-p-sm md:typo-p-md text-slate-500">مقایسه محصولات</span>
|
<span class="typo-p-sm md:typo-p-md text-slate-500">مقایسه محصولات</span>
|
||||||
<span class="typo-h-6 md:typo-h-5 lg:typo-h-3 text-black">
|
<span class="typo-h-6 md:typo-h-5 lg:typo-h-3 text-black">
|
||||||
تفاوت محصلات ما را ببینید
|
تفاوت محصلات ما را ببینید
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
ref="previewContainerEl"
|
ref="previewContainerEl"
|
||||||
class="rounded-200 overflow-hidden h-[70svh] md:h-[80svh] relative"
|
class="rounded-200 overflow-hidden h-[70svh] relative"
|
||||||
>
|
>
|
||||||
<Transition name="fade">
|
<Transition name="fade">
|
||||||
<NuxtImg
|
<NuxtImg
|
||||||
v-if="activeSlideVideo !== 'right'"
|
v-if="activeSlideVideo !== 'right'"
|
||||||
:src="homeData!.difreance_section.image1"
|
:src="homeData!.difreance_section.image1"
|
||||||
class="select-none absolute size-full object-cover brightness-[95%]"
|
:class="showHeymlzAnimation ? 'brightness-35' : 'brightness-[95%]'"
|
||||||
|
class="select-none absolute size-full object-cover transition-[filter] duration-250"
|
||||||
:alt="homeData!.difreance_section.title1"
|
:alt="homeData!.difreance_section.title1"
|
||||||
/>
|
/>
|
||||||
<video
|
<video
|
||||||
@@ -76,13 +95,14 @@ watch(
|
|||||||
/>
|
/>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<div class="absolute size-full right-0 w-full">
|
<div class="absolute size-full right-0 w-full" ref="heymlzElement">
|
||||||
|
|
||||||
<Transition name="fade">
|
<Transition name="fade">
|
||||||
<NuxtImg
|
<NuxtImg
|
||||||
v-if="activeSlideVideo !== 'left'"
|
v-if="activeSlideVideo !== 'left'"
|
||||||
:src="homeData!.difreance_section.image2"
|
:src="homeData!.difreance_section.image2"
|
||||||
class="overlay-image select-none absolute object-cover size-full brightness-[95%]"
|
:class="showHeymlzAnimation ? 'brightness-35' : 'brightness-[95%]'"
|
||||||
|
class="overlay-image select-none absolute object-cover size-full transition-[filter] duration-250"
|
||||||
:alt="homeData!.difreance_section.title2"
|
:alt="homeData!.difreance_section.title2"
|
||||||
/>
|
/>
|
||||||
<video
|
<video
|
||||||
@@ -97,12 +117,14 @@ watch(
|
|||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<video
|
<video
|
||||||
|
v-if="showHeymlzAnimation"
|
||||||
src="/video/heymlz/heymlz-pulling.webm"
|
src="/video/heymlz/heymlz-pulling.webm"
|
||||||
autoplay
|
autoplay
|
||||||
muted
|
muted
|
||||||
playsinline
|
playsinline
|
||||||
|
loop
|
||||||
webkit-playsinline
|
webkit-playsinline
|
||||||
class="size-[300px] absolute translate-x-[-100px] z-10 top-[32%] -translate-y-1/2"
|
class="size-[400px] absolute translate-x-[-107px] z-10 top-[50%] -translate-y-1/2"
|
||||||
:style="{
|
:style="{
|
||||||
left: `${clipPathPercent}%`,
|
left: `${clipPathPercent}%`,
|
||||||
filter: 'drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.3))'
|
filter: 'drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.3))'
|
||||||
@@ -113,17 +135,23 @@ watch(
|
|||||||
:style="{
|
:style="{
|
||||||
left: `${clipPathPercent}%`,
|
left: `${clipPathPercent}%`,
|
||||||
}"
|
}"
|
||||||
class="select-none w-2 h-full bg-black absolute left-0 flex items-center justify-center"
|
:class="[
|
||||||
|
activeSlideVideo !== 'none' ? 'opacity-10' : '',
|
||||||
|
showHeymlzAnimation ? 'bg-neutral-300' : 'bg-black'
|
||||||
|
]"
|
||||||
|
class="select-none w-2 h-full absolute left-0 flex items-center justify-center transition-opacity duration-250"
|
||||||
>
|
>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref="draggableEl"
|
ref="draggableEl"
|
||||||
class="cursor-grab hover:scale-115 transition-transform rounded-full absolute bg-black size-11 flex items-center justify-center"
|
:class="showHeymlzAnimation ? 'bg-neutral-300' : 'bg-black'"
|
||||||
|
class="cursor-grab hover:scale-115 transition-transform rounded-full absolute size-11 flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name="ci:arrows"
|
name="ci:arrows"
|
||||||
size="24"
|
size="24"
|
||||||
class="**:stroke-white"
|
class="transition-all"
|
||||||
|
:class="showHeymlzAnimation ? '**:stroke-black' : '**:stroke-white'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,10 +160,13 @@ watch(
|
|||||||
<div
|
<div
|
||||||
class="max-xs:hidden absolute bottom-0 p-6 md:p-10 w-full flex justify-between items-end"
|
class="max-xs:hidden absolute bottom-0 p-6 md:p-10 w-full flex justify-between items-end"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-2 text-black">
|
<div
|
||||||
<span class="typo-p-sm md:typo-p-md">
|
class="flex flex-col gap-2 text-black transition-opacity"
|
||||||
{{ homeData!.difreance_section.description1 }}
|
:class="activeSlideVideo === 'right' ? 'opacity-0' : ''"
|
||||||
</span>
|
>
|
||||||
|
<span class="typo-p-sm md:typo-p-md">
|
||||||
|
{{ homeData!.difreance_section.description1 }}
|
||||||
|
</span>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
:to="homeData!.difreance_section.link1"
|
:to="homeData!.difreance_section.link1"
|
||||||
class="typo-h-6 md:typo-h-5 lg:typo-h-3"
|
class="typo-h-6 md:typo-h-5 lg:typo-h-3"
|
||||||
@@ -143,10 +174,13 @@ watch(
|
|||||||
{{ homeData!.difreance_section.title1 }}
|
{{ homeData!.difreance_section.title1 }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 text-black">
|
<div
|
||||||
<span class="typo-p-sm md:typo-p-md text-end">
|
class="flex flex-col gap-2 text-black transition-opacity"
|
||||||
{{ homeData!.difreance_section.description2 }}
|
:class="activeSlideVideo === 'left' ? 'opacity-0' : ''"
|
||||||
</span>
|
>
|
||||||
|
<span class="typo-p-sm md:typo-p-md text-end">
|
||||||
|
{{ homeData!.difreance_section.description2 }}
|
||||||
|
</span>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
:to="homeData!.difreance_section.link2"
|
:to="homeData!.difreance_section.link2"
|
||||||
class="typo-h-6 md:typo-h-5 lg:typo-h-3 text-end"
|
class="typo-h-6 md:typo-h-5 lg:typo-h-3 text-end"
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ onUnmounted(() => {
|
|||||||
:to="slide.link"
|
:to="slide.link"
|
||||||
class="showcase-slide origin-bottom absolute size-full bg-transparent flex items-center justify-center"
|
class="showcase-slide origin-bottom absolute size-full bg-transparent flex items-center justify-center"
|
||||||
>
|
>
|
||||||
|
|
||||||
<NuxtImg
|
<NuxtImg
|
||||||
class="w-[280px] xs:w-[350px] lg:w-[500px] xl:w-[650px] z-20 mb-30 sm:mb-20 lg:mb-30"
|
class="w-[280px] xs:w-[350px] lg:w-[500px] xl:w-[650px] z-20 mb-30 sm:mb-20 lg:mb-30"
|
||||||
:src="slide.image"
|
:src="slide.image"
|
||||||
@@ -101,13 +100,22 @@ onUnmounted(() => {
|
|||||||
}"
|
}"
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-col items-center justify-center gap-4 text-center absolute z-20 mt-20">
|
<div class="flex flex-col items-center justify-center gap-6 text-center absolute z-20 mt-20">
|
||||||
<span class="text-white typo-h-6 sm:typo-h-5 lg:typo-h-4 xl:typo-h-3">
|
<span class="text-white typo-h-6 sm:typo-h-5 lg:typo-h-4 xl:typo-h-3">
|
||||||
{{ slide.title }}
|
{{ slide.title }}
|
||||||
</span>
|
</span>
|
||||||
<p class="text-white max-w-[320px] xs:max-w-[360px] sm:max-w-[480px] lg:max-w-[550px] xl:max-w-[750px] typo-p-sm lg:typo-p-md xl:typo-p-lg">
|
<p class="text-white max-w-[320px] xs:max-w-[360px] sm:max-w-[480px] lg:max-w-[550px] xl:max-w-[750px] typo-p-sm lg:typo-p-md xl:typo-p-lg">
|
||||||
{{ slide.description }}
|
{{ slide.description }}
|
||||||
</p>
|
</p>
|
||||||
|
<NuxtLink :to="slide.link">
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
end-icon="ci:arrow-left"
|
||||||
|
class="mt-8 max-sm:hidden max-lg:typo-label-xs px-10 rounded-full hover:bg-transparent"
|
||||||
|
>
|
||||||
|
مشاهده دسته بندی
|
||||||
|
</Button>
|
||||||
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,18 +21,18 @@ const { circle } = toRefs(props);
|
|||||||
<div
|
<div
|
||||||
:style="{
|
:style="{
|
||||||
height: `${size}px`,
|
height: `${size}px`,
|
||||||
width: circle ? `${size}px` : '100%',
|
width: circle ? `${size}px` : '100%'
|
||||||
}"
|
}"
|
||||||
class="relative flex items-center w-full justify-center shrink-0"
|
class="relative flex items-center w-full justify-center shrink-0"
|
||||||
:class="{
|
:class="{
|
||||||
'rounded-full overflow-hidden': circle,
|
'rounded-full overflow-hidden': circle,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<img
|
<NuxtImg
|
||||||
:style="{
|
:style="{
|
||||||
maskImage: 'radial-gradient(black, transparent 70%)'
|
maskImage: 'radial-gradient(black, transparent 70%)'
|
||||||
}"
|
}"
|
||||||
src="/public/img/ai-loading-2.gif"
|
src="/img/heymlz/heymlz-idle.gif"
|
||||||
class="size-full object-cover absolute pt-2"
|
class="size-full object-cover absolute pt-2"
|
||||||
alt="ai-loading"
|
alt="ai-loading"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ const { isLoggedIn } = useAuth();
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const id = route.params.id as string | number;
|
const id = route.params.id as string | number;
|
||||||
|
|
||||||
|
const scrollToBottomTimer = ref<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
const chatContainerEl = ref<HTMLElement | null>(null);
|
const chatContainerEl = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
const lastMessageBeforeUpdate = ref(0);
|
const lastMessageBeforeUpdate = ref(0);
|
||||||
@@ -63,7 +65,10 @@ useInfiniteScroll(
|
|||||||
// methods
|
// methods
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
chatContainerScrollY.value = chatContainerEl.value?.scrollHeight ?? 0;
|
if (scrollToBottomTimer.value) clearTimeout(scrollToBottomTimer.value);
|
||||||
|
scrollToBottomTimer.value = setTimeout(() => {
|
||||||
|
chatContainerScrollY.value = chatContainerEl.value?.scrollHeight ?? 0;
|
||||||
|
}, 50);
|
||||||
};
|
};
|
||||||
|
|
||||||
// computed
|
// computed
|
||||||
@@ -137,8 +142,7 @@ whenever(
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:style="{
|
:style="{
|
||||||
maskImage:
|
maskImage: 'linear-gradient(to top, transparent, black 5%, black, black)'
|
||||||
'linear-gradient(to top, transparent, black 5%, black, black)',
|
|
||||||
}"
|
}"
|
||||||
class="hide-scrollbar flex flex-col py-7 gap-6 h-full overflow-y-auto"
|
class="hide-scrollbar flex flex-col py-7 gap-6 h-full overflow-y-auto"
|
||||||
ref="chatContainerEl"
|
ref="chatContainerEl"
|
||||||
@@ -184,11 +188,20 @@ whenever(
|
|||||||
</Transition>
|
</Transition>
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div
|
||||||
class="text-black p-4.5 size-full flex justify-center items-center"
|
class="text-black p-6 size-full flex justify-center items-center flex-col"
|
||||||
v-else
|
v-else
|
||||||
>
|
>
|
||||||
<img class="size-[50px]" src="/img/heymlz/heymlz-idle.gif" alt="" />
|
<NuxtImg class="size-[250px]" src="/img/heymlz/heymlz-loading-1.gif" alt="" />
|
||||||
Please sign in first
|
<div class="flex flex-col gap-4 items-center">
|
||||||
|
<span class="text-center typo-p-xl font-bold">سلام دوست عزیز!</span>
|
||||||
|
<p class="text-center typo-p-md">
|
||||||
|
من میتونم هر سوالی رو درمورد این محصول جواب بدم
|
||||||
|
اگه میخوای شروع کنیم روی دکمه زیر کلیک کن
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<NuxtLink to="/signin">
|
||||||
|
<Button class="mt-8 rounded-full px-10">ورود به فروشگاه</Button>
|
||||||
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import { useToast } from "~/composables/global/useToast";
|
|||||||
|
|
||||||
// state
|
// state
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const id = route.params.id as string | number;
|
||||||
|
|
||||||
const { $queryClient: queryClient } = useNuxtApp();
|
const { $queryClient: queryClient } = useNuxtApp();
|
||||||
|
|
||||||
const { addToast } = useToast();
|
const { addToast } = useToast();
|
||||||
@@ -27,7 +30,7 @@ const sendMessage = async () => {
|
|||||||
|
|
||||||
await createMessage({
|
await createMessage({
|
||||||
new_message: value,
|
new_message: value,
|
||||||
productId: 1,
|
productId: id,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
addToast({
|
addToast({
|
||||||
|
|||||||
@@ -54,13 +54,12 @@ onMounted(() => {
|
|||||||
`#chat-message-content-${id.value}`,
|
`#chat-message-content-${id.value}`,
|
||||||
{
|
{
|
||||||
text: "",
|
text: "",
|
||||||
duration: 2.5,
|
|
||||||
ease: "none",
|
ease: "none",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: { value: content.value, rtl: false },
|
text: { value: content.value, rtl: false },
|
||||||
duration: 2.5,
|
|
||||||
ease: "none",
|
ease: "none",
|
||||||
|
duration: 2.5,
|
||||||
onUpdate: () => emit("textUpdate"),
|
onUpdate: () => emit("textUpdate"),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -78,9 +77,9 @@ onMounted(() => {
|
|||||||
<div
|
<div
|
||||||
class="relative overflow-hidden flex items-center justify-center mt-px bg-slate-300 rounded-full size-[35px] shrink-0"
|
class="relative overflow-hidden flex items-center justify-center mt-px bg-slate-300 rounded-full size-[35px] shrink-0"
|
||||||
>
|
>
|
||||||
<img
|
<NuxtImg
|
||||||
v-if="!reverse"
|
v-if="reverse"
|
||||||
src="/img/footer-bg.jpg"
|
src="/img/heymlz/footer-share.svg"
|
||||||
class="size-full object-cover absolute"
|
class="size-full object-cover absolute"
|
||||||
alt="profile"
|
alt="profile"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import { useAuth } from "~/composables/api/auth/useAuth";
|
|||||||
|
|
||||||
// types
|
// types
|
||||||
|
|
||||||
export type GetBranchResponse = ApiPaginated<Chat>;
|
export type GetChatResponse = ApiPaginated<Chat>;
|
||||||
|
|
||||||
const useGetBranch = (productId: string | number, enabled: Ref<boolean>) => {
|
const useGetChat = (productId: string | number, enabled: Ref<boolean>) => {
|
||||||
// state
|
// state
|
||||||
|
|
||||||
const { $axios: axios } = useNuxtApp();
|
const { $axios: axios } = useNuxtApp();
|
||||||
@@ -26,7 +26,7 @@ const useGetBranch = (productId: string | number, enabled: Ref<boolean>) => {
|
|||||||
limit: number;
|
limit: number;
|
||||||
offset: number;
|
offset: number;
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await axios.get<GetBranchResponse>(
|
const { data } = await axios.get<GetChatResponse>(
|
||||||
`${API_ENDPOINTS.chat.messages}/${productId}`,
|
`${API_ENDPOINTS.chat.messages}/${productId}`,
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
@@ -65,4 +65,4 @@ const useGetBranch = (productId: string | number, enabled: Ref<boolean>) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useGetBranch;
|
export default useGetChat;
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ const disableLoadingOverlay = useState("disableLoadingOverlay", () => false);
|
|||||||
const response = await suspense();
|
const response = await suspense();
|
||||||
|
|
||||||
if (response.isError) {
|
if (response.isError) {
|
||||||
console.log(response);
|
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
statusMessage: `Landing error : ${response.error.message}`
|
statusMessage: `Landing error : ${response.error.message}`
|
||||||
|
|||||||
@@ -160,8 +160,8 @@ const resetForm = () => {
|
|||||||
/>
|
/>
|
||||||
<div class="flex items-center justify-center flex-col size-full translate-y-[-80px]">
|
<div class="flex items-center justify-center flex-col size-full translate-y-[-80px]">
|
||||||
<video
|
<video
|
||||||
class="aspect-square w-[450px] translate-y-[197px] animate-fade-in"
|
class="aspect-square w-[450px] translate-y-[157px] animate-fade-in"
|
||||||
src="/video/heymlz/heymlz-seat-2.webm"
|
src="/video/heymlz/heymlz-handshake-full.webm"
|
||||||
:style="{
|
:style="{
|
||||||
filter: 'drop-shadow(0px 4px 20px rgba(0, 0, 0, 0.15))'
|
filter: 'drop-shadow(0px 4px 20px rgba(0, 0, 0, 0.15))'
|
||||||
}"
|
}"
|
||||||
@@ -179,9 +179,9 @@ const resetForm = () => {
|
|||||||
<form @submit.prevent class="max-w-[500px] w-full mt-12">
|
<form @submit.prevent class="max-w-[500px] w-full mt-12">
|
||||||
<Input
|
<Input
|
||||||
v-if="!showOtp"
|
v-if="!showOtp"
|
||||||
class="w-full tracking-[2px]"
|
class="w-full tracking-[3px] persian-number"
|
||||||
v-model="loginInfo.phone"
|
v-model="loginInfo.phone"
|
||||||
placeholder="9380123456"
|
placeholder="۹۳۸۰۱۲۳۴۵۶"
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
:error="formValidator$.phone.$error"
|
:error="formValidator$.phone.$error"
|
||||||
>
|
>
|
||||||
@@ -193,7 +193,7 @@ const resetForm = () => {
|
|||||||
size="24"
|
size="24"
|
||||||
/>
|
/>
|
||||||
<span class="text-slate-500 typo-label-sm">
|
<span class="text-slate-500 typo-label-sm">
|
||||||
+98
|
+۹۸
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 32 KiB |