Merge branch 'main' of https://github.com/Byeto-Company/hossein_por_shop
This commit is contained in:
+15
-13
@@ -1,7 +1,7 @@
|
|||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
from .models import *
|
from .models import *
|
||||||
from unfold.admin import TabularInline, StackedInline
|
from unfold.admin import TabularInline, StackedInline
|
||||||
|
from django.db.models import Q
|
||||||
from import_export.admin import ImportExportModelAdmin
|
from import_export.admin import ImportExportModelAdmin
|
||||||
from unfold.contrib.import_export.forms import ExportForm, ImportForm, SelectableFieldsExportForm
|
from unfold.contrib.import_export.forms import ExportForm, ImportForm, SelectableFieldsExportForm
|
||||||
from unfold.contrib.forms.widgets import ArrayWidget, WysiwygWidget
|
from unfold.contrib.forms.widgets import ArrayWidget, WysiwygWidget
|
||||||
@@ -47,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']
|
search_fields = ['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 = ['order_id', 'user', 'is_paid', 'status', 'discount_code', 'address',]
|
list_display = ['order_id', 'user', 'is_paid', 'status', 'discount_code', 'address',]
|
||||||
readonly_fields = ('created_at', 'order_id', 'tax', 'final_price', 'cart_total', 'discount', 'discount_code', 'user', 'address', 'is_paid')
|
readonly_fields = ('created_at', 'tax', 'final_price', 'cart_total', 'discount_amount', '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',)
|
||||||
@@ -61,19 +61,21 @@ class OrderAdmin(ModelAdmin, ImportExportModelAdmin):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
inlines = [OrderItemModelInline, BankRecordInline]
|
inlines = [OrderItemModelInline, BankRecordInline]
|
||||||
# def bank_links(self, obj):
|
def order_id(self, obj):
|
||||||
# banks = obj.bank_records.all()
|
return f"سفارش {obj.pk + 1000}"
|
||||||
|
order_id.short_description = "شماره سفارش"
|
||||||
|
|
||||||
# if not banks.exists():
|
|
||||||
# return "-"
|
|
||||||
|
|
||||||
# return format_html_join(
|
|
||||||
# "",
|
|
||||||
# '<a style="padding-bottom:10px;display:block;" href="/secret-admin/azbankgateways/bank/{}/change/" class="text-primary-600 dark:text-primary-500">{}</a>',
|
|
||||||
# [(bank.id, bank.tracking_code) for bank in banks]
|
|
||||||
# ) or "-"
|
|
||||||
|
|
||||||
# bank_links.short_description = "Bank Records"
|
def get_search_results(self, request, queryset, search_term):
|
||||||
|
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
|
||||||
|
|
||||||
|
|
||||||
|
if search_term.isdigit():
|
||||||
|
order_id_search = int(search_term) - 1000
|
||||||
|
queryset |= self.model.objects.filter(Q(pk=order_id_search))
|
||||||
|
|
||||||
|
return queryset, use_distinct
|
||||||
|
|
||||||
@action(description='اپدیت وضعیت رکورد های بانکی')
|
@action(description='اپدیت وضعیت رکورد های بانکی')
|
||||||
def udpate_bank_status(self, request):
|
def udpate_bank_status(self, request):
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-03-29 13:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('order', '0025_alter_ordermodel_order_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='orderitemmodel',
|
||||||
|
name='discount',
|
||||||
|
field=models.SmallIntegerField(verbose_name='درصد تخفیف'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='orderitemmodel',
|
||||||
|
name='price',
|
||||||
|
field=models.PositiveIntegerField(verbose_name='قیمت'),
|
||||||
|
),
|
||||||
|
]
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-03-30 15:00
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('order', '0026_alter_orderitemmodel_discount_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='orderitemmodel',
|
||||||
|
old_name='discount',
|
||||||
|
new_name='discount_percent',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='ordermodel',
|
||||||
|
old_name='discount',
|
||||||
|
new_name='discount_amount',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='ordermodel',
|
||||||
|
name='order_id',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='ordermodel',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(choices=[('CART', 'در سبد خرید'), ('ADMIN_PENDING', 'در انتظار تایید'), ('PENDING', 'درحال پردازش'), ('POSTED', 'ارسال شده'), ('RECEIVED', 'تحویل شده'), ('CANCELED', 'لغو شده'), ('REFUNDED', 'مرجوع شده')], default='CART', max_length=20, verbose_name='وضعیت سفارش'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-03-30 15:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('order', '0027_rename_discount_orderitemmodel_discount_percent_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='orderitemmodel',
|
||||||
|
name='price',
|
||||||
|
field=models.BigIntegerField(verbose_name='قیمت'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-03-30 16:55
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('order', '0028_alter_orderitemmodel_price'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='ordermodel',
|
||||||
|
name='discount_amount',
|
||||||
|
field=models.BigIntegerField(blank=True, null=True, verbose_name='مقدار کد تخفیف'),
|
||||||
|
),
|
||||||
|
]
|
||||||
+52
-42
@@ -1,9 +1,10 @@
|
|||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
from account.models import User, UserAddressModel, PushSubscription
|
from account.models import User, UserAddressModel, PushSubscription
|
||||||
from product.models import ProductModel, ProductVariant, ProductImageModel
|
from product.models import ProductModel, ProductVariant, ProductImageModel
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django_jalali.db import models as jmodels
|
from django_jalali.db import models as jmodels
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
class DiscountCode(models.Model):
|
class DiscountCode(models.Model):
|
||||||
code = models.CharField(max_length=50, verbose_name='کد تخفیف')
|
code = models.CharField(max_length=50, verbose_name='کد تخفیف')
|
||||||
@@ -44,20 +45,19 @@ 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="تاریخ ثبت سفارش")
|
||||||
is_paid = models.BooleanField(default=False, verbose_name="وضعیت پرداخت")
|
is_paid = models.BooleanField(default=False, verbose_name="وضعیت پرداخت")
|
||||||
discount_code = models.ForeignKey(DiscountCode, on_delete=models.PROTECT, null=True, blank=True, verbose_name="کدتخفیف")
|
discount_code = models.ForeignKey(DiscountCode, on_delete=models.PROTECT, null=True, blank=True, verbose_name="کدتخفیف")
|
||||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, verbose_name="وضعیت سفارش")
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='CART', verbose_name="وضعیت سفارش")
|
||||||
discount = models.BigIntegerField(null=True, blank=True, verbose_name='کل تخقیف')
|
discount_amount = models.BigIntegerField(null=True, blank=True, verbose_name='مقدار کد تخفیف')
|
||||||
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='کل سبد خرید')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'سفارش: {self.id + 1000}'
|
return f'سفارش: {self.pk + 1000}'
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'سفارش'
|
verbose_name = 'سفارش'
|
||||||
@@ -65,58 +65,68 @@ class OrderModel(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def _cal_discount_amount(self, cart_total):
|
||||||
# genrate order id
|
discount_percent = self.discount_code.percent if self.discount_code else 0
|
||||||
if not self.pk:
|
return int(cart_total * discount_percent / 100)
|
||||||
last_instance = self.__class__.objects.order_by("pk").last()
|
|
||||||
self.order_id = (last_instance.pk + 1001) if last_instance else 1001
|
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
|
def _cal_tax(self, cart_total, discount_amount):
|
||||||
|
tax_rate = getattr(settings, 'DEFAULT_TAX_RATE', 20)
|
||||||
def cal_discount(self):
|
return int((cart_total - discount_amount) * tax_rate / 100)
|
||||||
# total_with_item_discount = sum(item.total_with_discount() for item in self.items.all())
|
|
||||||
# discount_percent = self.discount_code.percent
|
|
||||||
# return total_with_item_discount * ((100 - discount_percent) / 100)
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def cal_tax(self):
|
|
||||||
return self.total_without_tax() * 0.2
|
|
||||||
|
|
||||||
|
def _cal_cart_total(self):
|
||||||
|
from django.db.models import Sum, F, FloatField
|
||||||
|
return self.items.aggregate(
|
||||||
|
total=Sum(F('price') * (1 - F('discount_percent')/100) * F('quantity'),
|
||||||
|
output_field=FloatField()
|
||||||
|
)).get('total') or 0
|
||||||
|
|
||||||
def cal_total(self):
|
def _cal_final_price(self, cart_total, discount_amount, tax):
|
||||||
pass
|
return cart_total - discount_amount + tax
|
||||||
return self.total_with_discount() + self.tax()
|
|
||||||
|
|
||||||
|
|
||||||
def cal_final_price(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def update_order(self):
|
||||||
|
if self.status == 'CART':
|
||||||
|
cart_total = self._cal_cart_total()
|
||||||
|
discount_amount = self._cal_discount_amount(cart_total)
|
||||||
|
self.discount_amount = discount_amount
|
||||||
|
self.cart_total = cart_total
|
||||||
|
tax = self._cal_tax(cart_total, discount_amount)
|
||||||
|
self.tax = tax
|
||||||
|
self.final_price = self._cal_final_price(cart_total, discount_amount, tax)
|
||||||
|
self.save()
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class OrderItemModel(models.Model):
|
class OrderItemModel(models.Model):
|
||||||
order = models.ForeignKey(OrderModel, on_delete=models.CASCADE, related_name='items', verbose_name='سفارش')
|
order = models.ForeignKey(OrderModel, on_delete=models.CASCADE, related_name='items', verbose_name='سفارش')
|
||||||
quantity = models.PositiveSmallIntegerField(verbose_name="تعداد")
|
quantity = models.PositiveSmallIntegerField(verbose_name="تعداد")
|
||||||
price = models.PositiveIntegerField(verbose_name='قیمت', default=0)
|
price = models.BigIntegerField(verbose_name='قیمت')
|
||||||
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='تخفیف')
|
discount_percent = models.SmallIntegerField(verbose_name='درصد تخفیف')
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'ایتم سبد خرید'
|
verbose_name = 'ایتم سبد خرید'
|
||||||
verbose_name_plural = 'ایتم های سبد خرید'
|
verbose_name_plural = 'ایتم های سبد خرید'
|
||||||
|
|
||||||
# def total(self):
|
|
||||||
# return self.quantity * self.product.price
|
|
||||||
|
|
||||||
# def total_with_discount(self):
|
def set_or_update_fields(self):
|
||||||
# return self.quantity * self.product.get_toman_price_after_discount()
|
self.price = self.product.price
|
||||||
|
self.discount_percent = self.product.discount
|
||||||
def update_fields(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'({self.product}) - ({self.order.user})'
|
return f'({self.product}) - ({self.order.user})'
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.clean()
|
||||||
|
self.set_or_update_fields()
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
self.order.update_order()
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
self.clean()
|
||||||
|
order = self.order
|
||||||
|
super().delete(*args, **kwargs)
|
||||||
|
order.update_order()
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if self.pk and self.order.status != "CART":
|
||||||
|
raise ValidationError("ایتم ها فقط در حالت سبد خرید قابل ادیت هستند")
|
||||||
@@ -53,7 +53,7 @@ class OrderItemSerailzier(serializers.ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = OrderItemModel
|
model = OrderItemModel
|
||||||
exclude = ('order',)
|
exclude = ('order',)
|
||||||
read_only_fields = ('order', 'product',)
|
read_only_fields = ('order', 'product', 'discount_percent')
|
||||||
def get_product(self, obj):
|
def get_product(self, obj):
|
||||||
return ProductVariantSerialzier(instance=obj.product, context={'request': self.context.get('request')}).data
|
return ProductVariantSerialzier(instance=obj.product, context={'request': self.context.get('request')}).data
|
||||||
|
|
||||||
@@ -79,9 +79,10 @@ class CartSerializer(serializers.ModelSerializer):
|
|||||||
tax = serializers.SerializerMethodField()
|
tax = serializers.SerializerMethodField()
|
||||||
final_price = serializers.SerializerMethodField()
|
final_price = serializers.SerializerMethodField()
|
||||||
discount_code = serializers.SerializerMethodField()
|
discount_code = serializers.SerializerMethodField()
|
||||||
|
address = UserAddressSerializer()
|
||||||
class Meta:
|
class Meta:
|
||||||
model = OrderModel
|
model = OrderModel
|
||||||
fields = [ 'discount_code', 'items', 'cart_total', 'tax', 'final_price']
|
fields = [ 'discount_code', 'items', 'cart_total', 'tax', 'final_price', 'address']
|
||||||
|
|
||||||
|
|
||||||
def get_discount_code(self, obj):
|
def get_discount_code(self, obj):
|
||||||
@@ -109,6 +110,7 @@ class OrderListSerializer(serializers.ModelSerializer):
|
|||||||
count = serializers.SerializerMethodField()
|
count = serializers.SerializerMethodField()
|
||||||
images = serializers.SerializerMethodField()
|
images = serializers.SerializerMethodField()
|
||||||
verbose_status = serializers.SerializerMethodField()
|
verbose_status = serializers.SerializerMethodField()
|
||||||
|
order_id = serializers.SerializerMethodField()
|
||||||
class Meta:
|
class Meta:
|
||||||
model = OrderModel
|
model = OrderModel
|
||||||
fields = ['created_at', 'status', "images", "count", "id", 'final_price', 'order_id', 'verbose_status']
|
fields = ['created_at', 'status', "images", "count", "id", 'final_price', 'order_id', 'verbose_status']
|
||||||
@@ -119,6 +121,8 @@ class OrderListSerializer(serializers.ModelSerializer):
|
|||||||
def get_count(self, obj):
|
def get_count(self, obj):
|
||||||
return obj.items.all().count()
|
return obj.items.all().count()
|
||||||
|
|
||||||
|
def get_order_id(self, obj):
|
||||||
|
return obj.pk + 1000
|
||||||
def get_images(self, obj):
|
def get_images(self, obj):
|
||||||
image_list = [
|
image_list = [
|
||||||
self.context.get('request').build_absolute_uri(image.image.url)
|
self.context.get('request').build_absolute_uri(image.image.url)
|
||||||
@@ -138,7 +142,7 @@ class OrderGetSerializer(serializers.ModelSerializer):
|
|||||||
discount_code = DiscountCodeSerializer()
|
discount_code = DiscountCodeSerializer()
|
||||||
class Meta:
|
class Meta:
|
||||||
model = OrderModel
|
model = OrderModel
|
||||||
fields = ['created_at', 'status', "images", "count", "id", 'final_price', 'order_id', 'verbose_status', 'address', 'items', 'tax' , 'cart_total', 'discount_code', 'discount']
|
fields = ['created_at', 'status', "images", "count", "id", 'final_price', 'order_id', 'verbose_status', 'address', 'items', 'tax' , 'cart_total', 'discount_code', 'discount_amount']
|
||||||
|
|
||||||
def get_verbose_status(self, obj):
|
def get_verbose_status(self, obj):
|
||||||
return obj.get_status_display()
|
return obj.get_status_display()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from django.db.models.signals import pre_save
|
from django.db.models.signals import pre_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from .models import OrderModel
|
from .models import OrderModel
|
||||||
from account.models import PushSubscription
|
from account.models import PushSubscription, UserAddressModel
|
||||||
import ghasedak_sms
|
import ghasedak_sms
|
||||||
from .tasks import send_change_status_notif, send_change_status_sms
|
from .tasks import send_change_status_notif, send_change_status_sms
|
||||||
|
|
||||||
@@ -13,8 +13,8 @@ def order_status_changed(sender, instance, **kwargs):
|
|||||||
|
|
||||||
if previous.status != instance.status:
|
if previous.status != instance.status:
|
||||||
new_status = instance.get_status_display()
|
new_status = instance.get_status_display()
|
||||||
send_change_status_notif.delay(instance.pk, new_status)
|
# send_change_status_notif.delay(instance.pk, new_status)
|
||||||
send_change_status_sms.delay(instance.pk, new_status)
|
# send_change_status_sms.delay(instance.pk, new_status)
|
||||||
|
|
||||||
if previous.status == 'CART' and instance.status == 'ADMIN_PENDING':
|
if previous.status == 'CART' and instance.status == 'ADMIN_PENDING':
|
||||||
# update_cart_price_fields()
|
# update_cart_price_fields()
|
||||||
@@ -23,11 +23,17 @@ def order_status_changed(sender, instance, **kwargs):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_save, sender=OrderModel)
|
||||||
|
def set_default_address(sender, instance, **kwargs):
|
||||||
|
if instance.address is None and instance.user:
|
||||||
|
default_address = UserAddressModel.objects.filter(user=instance.user, is_main=True).first()
|
||||||
|
if default_address:
|
||||||
|
instance.address = default_address
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def update_cart_price_fields(order):
|
# def update_cart_price_fields(order):
|
||||||
pass
|
# pass
|
||||||
|
|
||||||
def update_sell_data(order):
|
def update_sell_data(order):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ class BankCallbackSerializer(serializers.ModelSerializer):
|
|||||||
PaymentStatus.REDIRECT_TO_BANK,
|
PaymentStatus.REDIRECT_TO_BANK,
|
||||||
PaymentStatus.RETURN_FROM_BANK,
|
PaymentStatus.RETURN_FROM_BANK,
|
||||||
}:
|
}:
|
||||||
return "waiting"
|
return "pending"
|
||||||
elif obj.status in {
|
elif obj.status in {
|
||||||
PaymentStatus.CANCEL_BY_USER,
|
PaymentStatus.CANCEL_BY_USER,
|
||||||
PaymentStatus.EXPIRE_GATEWAY_TOKEN,
|
PaymentStatus.EXPIRE_GATEWAY_TOKEN,
|
||||||
|
|||||||
+7
-5
@@ -5,8 +5,7 @@ import { VueQueryDevtools } from "@tanstack/vue-query-devtools";
|
|||||||
|
|
||||||
// state
|
// state
|
||||||
|
|
||||||
const { $updateAvailable: updateAvailable, $handleUpdate: handleUpdate } =
|
const { $updateAvailable: updateAvailable, $handleUpdate: handleUpdate } = useNuxtApp();
|
||||||
useNuxtApp();
|
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
updateAvailable.value = false;
|
updateAvailable.value = false;
|
||||||
@@ -16,7 +15,7 @@ const closeModal = () => {
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
|
|
||||||
<NuxtPwaManifest />
|
<NuxtPwaManifest />
|
||||||
|
|
||||||
<UpdatePwaModal
|
<UpdatePwaModal
|
||||||
@@ -32,10 +31,13 @@ const closeModal = () => {
|
|||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
<ToastViewport
|
<ToastViewport
|
||||||
class="[--viewport-padding:_25px] fixed bottom-0 left-0 flex flex-col p-[var(--viewport-padding)] gap-[10px] w-[390px] max-w-[100vw] m-0 list-none z-[9999999999999999] outline-none"
|
class="[--viewport-padding:_25px] fixed bottom-0 left-1/2 -translate-x-1/2 flex flex-col p-[var(--viewport-padding)] gap-[10px] w-[390px] max-w-[100vw] m-0 list-none z-[9999999999999999] outline-none"
|
||||||
/>
|
/>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
|
|
||||||
<VueQueryDevtools dir="ltr" buttonPosition="top-right"/>
|
<VueQueryDevtools
|
||||||
|
dir="ltr"
|
||||||
|
buttonPosition="top-right"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -215,22 +215,22 @@
|
|||||||
@keyframes toastSlideIn {
|
@keyframes toastSlideIn {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateX(calc(100% + var(--viewport-padding)));
|
transform: translateY(calc(100% + var(--viewport-padding)));
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateX(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes toastSlideOut {
|
@keyframes toastSlideOut {
|
||||||
from {
|
from {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateX(var(--reka-toast-swipe-end-x));
|
transform: translateY(var(--reka-toast-swipe-end-x));
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateX(calc(100% + var(--viewport-padding)));
|
transform: translateY(calc(100% + var(--viewport-padding)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,6 +281,10 @@
|
|||||||
|
|
||||||
/* CONTAINER */
|
/* CONTAINER */
|
||||||
|
|
||||||
|
* {
|
||||||
|
scroll-behavior: smooth !important;
|
||||||
|
}
|
||||||
|
|
||||||
@utility container {
|
@utility container {
|
||||||
@apply mx-auto px-[var(--app-container-padding)] w-full max-sm:max-w-[var(--breakpoint-xs)] max-md:max-w-[var(--breakpoint-sm)] max-lg:max-w-[var(--breakpoint-md)] max-xl:max-w-[var(--breakpoint-lg)] max-w-[var(--breakpoint-2xl)];
|
@apply mx-auto px-[var(--app-container-padding)] w-full max-sm:max-w-[var(--breakpoint-xs)] max-md:max-w-[var(--breakpoint-sm)] max-lg:max-w-[var(--breakpoint-md)] max-xl:max-w-[var(--breakpoint-lg)] max-w-[var(--breakpoint-2xl)];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// imports
|
// imports
|
||||||
|
|
||||||
import useDeleteAddress from "~/composables/api/account/useDeleteAddress";
|
import useDeleteAddress from "~/composables/api/account/useDeleteAddress";
|
||||||
|
import useSetOrderAddress from "~/composables/api/orders/useSetOrderAddress";
|
||||||
import { useToast } from "~/composables/global/useToast";
|
import { useToast } from "~/composables/global/useToast";
|
||||||
import { QUERY_KEYS } from "~/constants";
|
import { QUERY_KEYS } from "~/constants";
|
||||||
|
|
||||||
@@ -15,13 +16,11 @@ type Props = {
|
|||||||
|
|
||||||
// props
|
// props
|
||||||
|
|
||||||
withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
selectable: true,
|
selectable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// emit
|
const { address } = toRefs(props);
|
||||||
|
|
||||||
const emit = defineEmits(["select"]);
|
|
||||||
|
|
||||||
// state
|
// state
|
||||||
|
|
||||||
@@ -31,19 +30,48 @@ const { addToast } = useToast();
|
|||||||
|
|
||||||
// queries
|
// queries
|
||||||
|
|
||||||
const { mutateAsync: deleteAddress, isPending: deleteAddressIsPending } =
|
const { mutateAsync: deleteAddress, isPending: deleteAddressIsPending } = useDeleteAddress();
|
||||||
useDeleteAddress();
|
|
||||||
|
const { mutateAsync: setOrderAddress, isPending: setOrderAddressIsPending } = useSetOrderAddress();
|
||||||
|
|
||||||
// methods
|
// methods
|
||||||
|
|
||||||
|
const handleSelectAddress = () => {
|
||||||
|
setOrderAddress(
|
||||||
|
{ address_id: address.value?.id! },
|
||||||
|
{
|
||||||
|
onSettled: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [QUERY_KEYS.cart],
|
||||||
|
});
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [QUERY_KEYS.addresses],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
addToast({
|
||||||
|
message: "در انتخاب آدرس خطایی رخ داد",
|
||||||
|
options: {
|
||||||
|
description: "لطفا مجدد تلاش کنید",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDeleteAddress = (id: number) => {
|
const handleDeleteAddress = (id: number) => {
|
||||||
deleteAddress(
|
deleteAddress(
|
||||||
{ id },
|
{ id },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [QUERY_KEYS.cart],
|
||||||
|
});
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: [QUERY_KEYS.addresses],
|
queryKey: [QUERY_KEYS.addresses],
|
||||||
});
|
});
|
||||||
|
|
||||||
addToast({
|
addToast({
|
||||||
message: "آدرس با موفقیت حذف شد",
|
message: "آدرس با موفقیت حذف شد",
|
||||||
options: {
|
options: {
|
||||||
@@ -66,29 +94,30 @@ const handleDeleteAddress = (id: number) => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
@click.prevent="
|
@click.prevent="!!address && selectable ? handleSelectAddress() : null"
|
||||||
!!address && selectable ? emit('select', address) : null
|
:class="isSelected ? 'border-transparent ring-2 ring-offset-2 ring-blue-500' : 'border-slate-200'"
|
||||||
"
|
|
||||||
:class="
|
|
||||||
isSelected
|
|
||||||
? 'border-transparent ring-2 ring-offset-2 ring-blue-500'
|
|
||||||
: 'border-slate-200'
|
|
||||||
"
|
|
||||||
class="flex flex-col items-center transition-all relative cursor-pointer w-full group gap-2 lg:gap-4 p-4 border rounded-xl bg-slate-50 overflow-hidden"
|
class="flex flex-col items-center transition-all relative cursor-pointer w-full group gap-2 lg:gap-4 p-4 border rounded-xl bg-slate-50 overflow-hidden"
|
||||||
>
|
>
|
||||||
<div v-if="deleteAddressIsPending" class="absolute inset-0">
|
<div
|
||||||
|
v-if="deleteAddressIsPending"
|
||||||
|
class="absolute inset-0"
|
||||||
|
>
|
||||||
<Skeleton class="!size-full !rounded-xl" />
|
<Skeleton class="!size-full !rounded-xl" />
|
||||||
</div>
|
</div>
|
||||||
<span class="flex items-center justify-between w-full gap-3">
|
<span class="flex items-center justify-between w-full gap-3">
|
||||||
<div
|
<div class="flex items-center gap-3 max-lg:text-sm font-semibold text-slate-900">
|
||||||
class="flex items-center gap-3 max-lg:text-sm font-semibold text-slate-900"
|
|
||||||
>
|
|
||||||
{{ !!address ? address.name : "آدرس" }}
|
{{ !!address ? address.name : "آدرس" }}
|
||||||
<span
|
<span
|
||||||
v-if="isSelected"
|
v-if="isSelected || setOrderAddressIsPending"
|
||||||
class="bg-blue-500 rounded-lg px-3 py-2 text-slate-200 text-[10px] lg:text-xs"
|
class="bg-blue-500 rounded-lg px-3 py-2 text-slate-200 text-[10px] lg:text-xs"
|
||||||
>
|
>
|
||||||
انتخاب شده
|
<span v-if="setOrderAddressIsPending">
|
||||||
|
<Icon
|
||||||
|
name="svg-spinners:3-dots-bounce"
|
||||||
|
class="**:fill-white"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span v-else-if="isSelected && !setOrderAddressIsPending"> انتخاب شده </span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -97,13 +126,14 @@ const handleDeleteAddress = (id: number) => {
|
|||||||
@click.stop="handleDeleteAddress(address.id!)"
|
@click.stop="handleDeleteAddress(address.id!)"
|
||||||
class="size-8 bg-slate-200/50 rounded-sm flex-center me-2 opacity-0 group-hover:opacity-100 transition-opacity"
|
class="size-8 bg-slate-200/50 rounded-sm flex-center me-2 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||||
>
|
>
|
||||||
<Icon name="bi:trash" class="**:fill-red-500" />
|
<Icon
|
||||||
|
name="bi:trash"
|
||||||
|
class="**:fill-red-500"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div
|
<div class="flex flex-col items-center justify-between w-full gap-3 lg:gap-8 lg:flex-row">
|
||||||
class="flex flex-col items-center justify-between w-full gap-3 lg:gap-8 lg:flex-row"
|
|
||||||
>
|
|
||||||
<div class="w-full lg:w-9/12 overflow-hidden">
|
<div class="w-full lg:w-9/12 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
class="w-full overflow-hidden overflow-ellipsis gap-5 text-start whitespace-pre text-xs lg:text-sm text-slate-700"
|
class="w-full overflow-hidden overflow-ellipsis gap-5 text-start whitespace-pre text-xs lg:text-sm text-slate-700"
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import useCreateOrUpdateAddress from "~/composables/api/account/useCreateOrUpdat
|
|||||||
import useGetAccount from "~/composables/api/account/useGetAccount";
|
import useGetAccount from "~/composables/api/account/useGetAccount";
|
||||||
import { QUERY_KEYS } from "~/constants";
|
import { QUERY_KEYS } from "~/constants";
|
||||||
import { useToast } from "~/composables/global/useToast";
|
import { useToast } from "~/composables/global/useToast";
|
||||||
|
import useVuelidate from "@vuelidate/core";
|
||||||
|
import { helpers, required, minLength } from "@vuelidate/validators";
|
||||||
|
|
||||||
// types
|
// types
|
||||||
|
|
||||||
@@ -42,6 +44,35 @@ const addressData = ref({
|
|||||||
is_main: address.value?.is_main ?? false,
|
is_main: address.value?.is_main ?? false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// computed
|
||||||
|
|
||||||
|
const formRules = computed(() => {
|
||||||
|
return {
|
||||||
|
province: {
|
||||||
|
required: helpers.withMessage("فیلد استان سکونت الزامی می باشد", required),
|
||||||
|
minLength: helpers.withMessage("فیلد استان سکونت حداقل 2 کرکتر می باشد", minLength(2)),
|
||||||
|
},
|
||||||
|
city: {
|
||||||
|
required: helpers.withMessage("فیلد شهر سکونت الزامی می باشد", required),
|
||||||
|
minLength: helpers.withMessage("فیلد شهر سکونت حداقل 2 کرکتر می باشد", minLength(2)),
|
||||||
|
},
|
||||||
|
postal_code: {
|
||||||
|
required: helpers.withMessage("فیلد کد پستی الزامی می باشد", required),
|
||||||
|
minLength: helpers.withMessage("فیلد کد پستی حداقل 10 کرکتر می باشد", minLength(10)),
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
required: helpers.withMessage("فیلد آدرس کامل الزامی می باشد", required),
|
||||||
|
minLength: helpers.withMessage("فیلد آدرس کامل حداقل 2 کرکتر می باشد", minLength(2)),
|
||||||
|
},
|
||||||
|
phone: {
|
||||||
|
required: helpers.withMessage("فیلد تلفن همراه الزامی می باشد", required),
|
||||||
|
minLength: helpers.withMessage("فیلد تلفن همراه حداقل 10 کرکتر می باشد", minLength(10)),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const formValidator$ = useVuelidate(formRules, addressData);
|
||||||
|
|
||||||
// queries
|
// queries
|
||||||
|
|
||||||
const { data: account } = useGetAccount();
|
const { data: account } = useGetAccount();
|
||||||
@@ -64,35 +95,42 @@ const closeModal = () => {
|
|||||||
is_main: false,
|
is_main: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
formValidator$.value.$reset();
|
||||||
isShow.value = false;
|
isShow.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addNew = () => {
|
const handleSubmit = async () => {
|
||||||
createOrUpdateAddress(
|
await formValidator$.value.$validate();
|
||||||
{ ...addressData.value },
|
if (!formValidator$.value.$errors.length) {
|
||||||
{
|
createOrUpdateAddress(
|
||||||
onSuccess: () => {
|
{ ...addressData.value },
|
||||||
queryClient.invalidateQueries({
|
{
|
||||||
queryKey: [QUERY_KEYS.addresses],
|
onSuccess: () => {
|
||||||
});
|
queryClient.invalidateQueries({
|
||||||
closeModal();
|
queryKey: [QUERY_KEYS.addresses],
|
||||||
addToast({
|
});
|
||||||
message: isEditing.value ? "آدرس با موفقیت ویرایش شد" : "آدرس با موفقیت اضافه شد",
|
queryClient.invalidateQueries({
|
||||||
options: {
|
queryKey: [QUERY_KEYS.cart],
|
||||||
status: "success",
|
});
|
||||||
},
|
closeModal();
|
||||||
});
|
addToast({
|
||||||
},
|
message: isEditing.value ? "آدرس با موفقیت ویرایش شد" : "آدرس با موفقیت اضافه شد",
|
||||||
onError: () => {
|
options: {
|
||||||
addToast({
|
status: "success",
|
||||||
message: isEditing.value ? "آدرس با موفقیت ویرایش شد" : "مشکلی در افزودن آدرس رخ داد",
|
},
|
||||||
options: {
|
});
|
||||||
status: "error",
|
},
|
||||||
},
|
onError: () => {
|
||||||
});
|
addToast({
|
||||||
},
|
message: isEditing.value ? "آدرس با موفقیت ویرایش شد" : "مشکلی در افزودن آدرس رخ داد",
|
||||||
}
|
options: {
|
||||||
);
|
status: "error",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -122,9 +160,9 @@ watch(
|
|||||||
<Button
|
<Button
|
||||||
:end-icon="!!address ? 'bi:pen' : 'ci:plus'"
|
:end-icon="!!address ? 'bi:pen' : 'ci:plus'"
|
||||||
size="md"
|
size="md"
|
||||||
class="rounded-full"
|
class="rounded-full transition-all"
|
||||||
:variant="!!address ? 'ghost' : 'solid'"
|
:variant="!!address ? 'ghost' : 'solid'"
|
||||||
:class="!!address ? '!bg-transparent !underline' : ''"
|
:class="!!address ? '!bg-transparent !underline underline-offset-4' : ''"
|
||||||
>
|
>
|
||||||
<span class="whitespace-pre max-lg:text-xs">
|
<span class="whitespace-pre max-lg:text-xs">
|
||||||
{{ !!address ? "ویرایش" : "افزودن آدرس" }}
|
{{ !!address ? "ویرایش" : "افزودن آدرس" }}
|
||||||
@@ -138,115 +176,111 @@ watch(
|
|||||||
dir="rtl"
|
dir="rtl"
|
||||||
>
|
>
|
||||||
<div class="grid w-full grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
<div class="grid w-full grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
<div class="flex flex-col gap-2">
|
<DataField
|
||||||
<label
|
id="name"
|
||||||
for="name"
|
label="نام پیش فرض"
|
||||||
class="text-xs font-semibold lg:text-sm text-gray-900"
|
>
|
||||||
>نام پیش فرض <span class="text-sm text-red-500">*</span></label
|
|
||||||
>
|
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="name"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="اینجا وارد کنید ..."
|
placeholder="اینجا وارد کنید ..."
|
||||||
v-model="addressData.name!"
|
v-model="addressData.name!"
|
||||||
/>
|
/>
|
||||||
</div>
|
</DataField>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label
|
|
||||||
for="province"
|
|
||||||
class="text-xs font-semibold lg:text-sm text-gray-900"
|
|
||||||
>
|
|
||||||
آدرس شما؟
|
|
||||||
<span class="text-sm text-red-500"> * </span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
|
<DataField
|
||||||
|
id="for_me"
|
||||||
|
label="آدرس شما؟"
|
||||||
|
:required="true"
|
||||||
|
>
|
||||||
<Select
|
<Select
|
||||||
|
id="for_me"
|
||||||
:options="['بله', 'خیر']"
|
:options="['بله', 'خیر']"
|
||||||
placeholder="انتخاب کنید"
|
placeholder="انتخاب کنید"
|
||||||
v-model="addressData.for_me as string"
|
v-model="addressData.for_me as string"
|
||||||
/>
|
/>
|
||||||
</div>
|
</DataField>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label
|
|
||||||
for="phone"
|
|
||||||
class="text-xs font-semibold lg:text-sm text-gray-900"
|
|
||||||
>شماره تلفن <span class="text-sm text-red-500">*</span></label
|
|
||||||
>
|
|
||||||
|
|
||||||
|
<DataField
|
||||||
|
id="phone"
|
||||||
|
label="تلفن همراه"
|
||||||
|
:required="true"
|
||||||
|
:error="formValidator$.phone"
|
||||||
|
>
|
||||||
<Input
|
<Input
|
||||||
id="phone"
|
id="phone"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="اینجا وارد کنید ..."
|
placeholder="اینجا وارد کنید ..."
|
||||||
|
:error="formValidator$.phone.$error"
|
||||||
v-model="addressData.phone!"
|
v-model="addressData.phone!"
|
||||||
/>
|
/>
|
||||||
</div>
|
</DataField>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label
|
|
||||||
for="province"
|
|
||||||
class="text-xs font-semibold lg:text-sm text-gray-900"
|
|
||||||
>استان
|
|
||||||
<span class="text-sm text-red-500">*</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
|
<DataField
|
||||||
|
id="province"
|
||||||
|
label="استان"
|
||||||
|
:required="true"
|
||||||
|
:error="formValidator$.province"
|
||||||
|
>
|
||||||
<Input
|
<Input
|
||||||
id="province"
|
id="province"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="اینجا وارد کنید ..."
|
placeholder="اینجا وارد کنید ..."
|
||||||
|
:error="formValidator$.province.$error"
|
||||||
v-model="addressData.province!"
|
v-model="addressData.province!"
|
||||||
/>
|
/>
|
||||||
</div>
|
</DataField>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<DataField
|
||||||
<label
|
id="city"
|
||||||
for="city"
|
label="شهر"
|
||||||
class="text-xs font-semibold lg:text-sm text-gray-900"
|
:required="true"
|
||||||
>شهر <span class="text-sm text-red-500">*</span></label
|
:error="formValidator$.city"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
id="city"
|
id="city"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="اینجا وارد کنید ..."
|
placeholder="اینجا وارد کنید ..."
|
||||||
|
:error="formValidator$.city.$error"
|
||||||
v-model="addressData.city!"
|
v-model="addressData.city!"
|
||||||
/>
|
/>
|
||||||
</div>
|
</DataField>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<DataField
|
||||||
<label
|
id="postal_code"
|
||||||
for="post"
|
label="کد پستی"
|
||||||
class="text-xs font-semibold lg:text-sm text-gray-900"
|
:required="true"
|
||||||
>کد پستی <span class="text-sm text-red-500">*</span></label
|
:error="formValidator$.postal_code"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
id="post"
|
id="postal_code"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="اینجا وارد کنید ..."
|
placeholder="اینجا وارد کنید ..."
|
||||||
|
:error="formValidator$.postal_code.$error"
|
||||||
v-model="addressData.postal_code!"
|
v-model="addressData.postal_code!"
|
||||||
/>
|
/>
|
||||||
</div>
|
</DataField>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col w-full gap-2">
|
<DataField
|
||||||
<label
|
id="address"
|
||||||
for="address"
|
label="آدرس کامل"
|
||||||
class="text-xs font-semibold lg:text-sm text-gray-900"
|
:required="true"
|
||||||
>آدرس کامل <span class="text-sm text-red-500">*</span></label
|
:error="formValidator$.address"
|
||||||
>
|
>
|
||||||
<textarea
|
<Textarea
|
||||||
id="address"
|
id="address"
|
||||||
placeholder="آدرس خود را بنویسید"
|
placeholder="آدرس خود را بنویسید"
|
||||||
v-model="addressData.address"
|
v-model="addressData.address"
|
||||||
class="flex items-center field-sizing-content resize-none bg-slate-50 border-slate-200 hover:border-black focus:border-black max-h-[10rem] text-black justify-between cursor-text transition-all border-[1.5px] gap-3 typo-label-md px-4 py-1.5 lg:py-3.5 selection:bg-slate-100 rounded-md lg:rounded-100 outline-none flex-1 text-xs lg:!text-sm placeholder-slate-400 placeholder:text-xs lg:placeholder:text-sm placeholder:font-normal"
|
:error="formValidator$.address.$error"
|
||||||
></textarea>
|
class="flex items-center field-sizing-content resize-none bg-slate-50 border-slate-200 hover:border-black focus:border-black max-h-[10rem] text-black justify-between cursor-text transition-all border-[1.5px] gap-3 typo-label-md px-4 py-2.5 lg:py-3 leading-[175%] selection:bg-slate-100 rounded-md lg:rounded-100 outline-none max-lg:h-[5rem] lg:flex-1 text-xs lg:!text-sm placeholder-slate-400 placeholder:text-xs lg:placeholder:text-sm placeholder:font-normal"
|
||||||
</div>
|
></Textarea>
|
||||||
|
</DataField>
|
||||||
|
|
||||||
<div class="flex items-center justify-between w-full gap-2">
|
<div class="flex items-center justify-between w-full gap-2">
|
||||||
<label
|
<label
|
||||||
for="is_main"
|
for="is_main"
|
||||||
class="text-xs font-semibold lg:text-sm text-gray-900"
|
class="text-xs font-medium lg:text-sm text-gray-900"
|
||||||
>
|
>
|
||||||
به عنوان آدرس پیش فرض ثبت شود؟
|
به عنوان آدرس پیش فرض ثبت شود؟
|
||||||
</label>
|
</label>
|
||||||
@@ -260,7 +294,7 @@ watch(
|
|||||||
<div class="py-6 border-t border-slate-200 flex gap-3">
|
<div class="py-6 border-t border-slate-200 flex gap-3">
|
||||||
<Button
|
<Button
|
||||||
:disabled="createAddressIsPending"
|
:disabled="createAddressIsPending"
|
||||||
@click="addNew"
|
@click="handleSubmit"
|
||||||
class="rounded-full px-10"
|
class="rounded-full px-10"
|
||||||
size="md"
|
size="md"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -13,12 +13,8 @@ const { data: cart, isLoading: cartIsLoading } = useGetCartOrders();
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="flex flex-col items-center w-full gap-4 p-4 border lg:gap-6 border-slate-200 rounded-xl bg-gray-50">
|
||||||
class="flex flex-col items-center w-full gap-4 p-4 border lg:gap-6 border-gray-300 rounded-xl bg-gray-50"
|
<span class="flex items-center justify-start w-full lg:text-[1.125rem] font-semibold text-gray-900">
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="flex items-center justify-start w-full lg:text-[1.125rem] font-semibold text-gray-900"
|
|
||||||
>
|
|
||||||
خلاصه سفارش
|
خلاصه سفارش
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -52,7 +48,10 @@ const { data: cart, isLoading: cartIsLoading } = useGetCartOrders();
|
|||||||
class="gap-2 flex-center"
|
class="gap-2 flex-center"
|
||||||
>
|
>
|
||||||
<span class="text-sm text-black"> مشاهده بیشتر </span>
|
<span class="text-sm text-black"> مشاهده بیشتر </span>
|
||||||
<Icon name="bi:chevron-down" class="**:stroke-black" />
|
<Icon
|
||||||
|
name="bi:chevron-down"
|
||||||
|
class="**:stroke-black"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const { $queryClient: queryClient } = useNuxtApp();
|
|||||||
const { addToast } = useToast();
|
const { addToast } = useToast();
|
||||||
|
|
||||||
const counter = ref(data.value.quantity);
|
const counter = ref(data.value.quantity);
|
||||||
const debouncedCounter = refDebounced(counter, 700);
|
const debouncedCounter = refDebounced(counter, 500);
|
||||||
|
|
||||||
const { isLoading: cartImageIsLoading } = useImage({
|
const { isLoading: cartImageIsLoading } = useImage({
|
||||||
src: data.value.product.image,
|
src: data.value.product.image,
|
||||||
@@ -34,8 +34,7 @@ const { isLoading: cartImageIsLoading } = useImage({
|
|||||||
|
|
||||||
// queries
|
// queries
|
||||||
|
|
||||||
const { mutateAsync: deleteCartItem, isPending: deleteCartItemIsPending } =
|
const { mutateAsync: deleteCartItem, isPending: deleteCartItemIsPending } = useDeleteCartItem();
|
||||||
useDeleteCartItem();
|
|
||||||
|
|
||||||
const { mutateAsync: addCartItem } = useAddCartItem();
|
const { mutateAsync: addCartItem } = useAddCartItem();
|
||||||
|
|
||||||
@@ -96,6 +95,7 @@ watch(
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
invalidateCart();
|
invalidateCart();
|
||||||
|
queryClient.refetchQueries({ queryKey: [QUERY_KEYS.product, data.value.product.id] });
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
invalidateCart();
|
invalidateCart();
|
||||||
@@ -134,24 +134,23 @@ watch(
|
|||||||
|
|
||||||
<div class="flex flex-col w-full gap-3 lg:gap-4">
|
<div class="flex flex-col w-full gap-3 lg:gap-4">
|
||||||
<div class="flex items-center justify-between gap-3">
|
<div class="flex items-center justify-between gap-3">
|
||||||
<span
|
<span class="font-semibold typo-sub-h-xs lg:typo-sub-h-sm text-slate-600">
|
||||||
class="font-semibold typo-sub-h-xs lg:typo-sub-h-sm text-slate-600"
|
|
||||||
>
|
|
||||||
{{ data.product.category }}
|
{{ data.product.category }}
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
v-if="data.discount > 0"
|
v-if="data.discount > 0"
|
||||||
class="text-white bg-blue-500 px-3 lg:px-4 py-1.5 lg:py-2 text-[10px] lg:text-xs rounded-full flex items-center gap-1"
|
class="text-white bg-blue-500 px-3 lg:px-4 py-1.5 lg:py-2 text-[10px] lg:text-xs rounded-full flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<Icon name="bi:percent" class="size-4" />
|
<Icon
|
||||||
|
name="bi:percent"
|
||||||
|
class="size-4"
|
||||||
|
/>
|
||||||
{{ data.discount }}
|
{{ data.discount }}
|
||||||
تخفیف
|
تخفیف
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span
|
<span class="font-semibold typo-sub-h-sm lg:typo-sub-h-xl text-black">
|
||||||
class="font-semibold typo-sub-h-sm lg:typo-sub-h-xl text-black"
|
|
||||||
>
|
|
||||||
{{ data.product.title }}
|
{{ data.product.title }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -171,8 +170,7 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
v-if="data.product.product_attributes.length > 0"
|
v-if="data.product.product_attributes.length > 0"
|
||||||
v-for="(variant, index) in data.product
|
v-for="(variant, index) in data.product.product_attributes"
|
||||||
.product_attributes"
|
|
||||||
:index="index"
|
:index="index"
|
||||||
class="px-3 py-1 rounded-full border border-slate-200 text-xs lg:text-sm"
|
class="px-3 py-1 rounded-full border border-slate-200 text-xs lg:text-sm"
|
||||||
>
|
>
|
||||||
@@ -180,20 +178,17 @@ watch(
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="items-center justify-between hidden w-full lg:flex -mt-1">
|
||||||
class="items-center justify-between hidden w-full lg:flex -mt-1"
|
|
||||||
>
|
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<button
|
<button
|
||||||
@click="handleIncreaseQuantity"
|
@click="handleIncreaseQuantity"
|
||||||
class="border size-10 flex-center rounded-100 border-slate-300"
|
class="border size-10 flex-center rounded-100 border-slate-300"
|
||||||
:class="
|
:class="deleteCartItemIsPending ? 'pointer-events-none' : ''"
|
||||||
deleteCartItemIsPending
|
|
||||||
? 'pointer-events-none'
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<Icon name="bi:plus" class="**:stroke-slate-800" />
|
<Icon
|
||||||
|
name="bi:plus"
|
||||||
|
class="**:stroke-slate-800"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="size-10 flex-center">{{ counter }}</div>
|
<div class="size-10 flex-center">{{ counter }}</div>
|
||||||
@@ -201,19 +196,11 @@ watch(
|
|||||||
<button
|
<button
|
||||||
@click="handleDecreaseQuantity"
|
@click="handleDecreaseQuantity"
|
||||||
class="border size-10 flex-center rounded-100 border-slate-300"
|
class="border size-10 flex-center rounded-100 border-slate-300"
|
||||||
:class="
|
:class="deleteCartItemIsPending ? 'pointer-events-none' : ''"
|
||||||
deleteCartItemIsPending
|
|
||||||
? 'pointer-events-none'
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
v-if="counter == 1"
|
v-if="counter == 1"
|
||||||
:name="
|
:name="deleteCartItemIsPending ? 'svg-spinners:3-dots-bounce' : 'bi:trash'"
|
||||||
deleteCartItemIsPending
|
|
||||||
? 'svg-spinners:3-dots-bounce'
|
|
||||||
: 'bi:trash'
|
|
||||||
"
|
|
||||||
class="**:fill-red-700"
|
class="**:fill-red-700"
|
||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
@@ -232,9 +219,7 @@ watch(
|
|||||||
>
|
>
|
||||||
{{ data.price }}
|
{{ data.price }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span class="typo-p-xl relative flex-center w-fit font-medium">
|
||||||
class="typo-p-xl relative flex-center w-fit font-medium"
|
|
||||||
>
|
|
||||||
{{ data.final_price }}
|
{{ data.final_price }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -248,11 +233,12 @@ watch(
|
|||||||
<button
|
<button
|
||||||
@click="handleIncreaseQuantity"
|
@click="handleIncreaseQuantity"
|
||||||
class="border size-7 p-1 lg:size-10 flex-center rounded-50 border-slate-300"
|
class="border size-7 p-1 lg:size-10 flex-center rounded-50 border-slate-300"
|
||||||
:class="
|
:class="deleteCartItemIsPending ? 'pointer-events-none' : ''"
|
||||||
deleteCartItemIsPending ? 'pointer-events-none' : ''
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<Icon name="bi:plus" class="**:stroke-slate-800" />
|
<Icon
|
||||||
|
name="bi:plus"
|
||||||
|
class="**:stroke-slate-800"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="size-10 text-sm flex-center">
|
<div class="size-10 text-sm flex-center">
|
||||||
@@ -262,20 +248,18 @@ watch(
|
|||||||
<button
|
<button
|
||||||
@click="handleDecreaseQuantity"
|
@click="handleDecreaseQuantity"
|
||||||
class="border size-7 lg:size-10 p-1 flex-center rounded-50 border-slate-300"
|
class="border size-7 lg:size-10 p-1 flex-center rounded-50 border-slate-300"
|
||||||
:class="
|
:class="deleteCartItemIsPending ? 'pointer-events-none' : ''"
|
||||||
deleteCartItemIsPending ? 'pointer-events-none' : ''
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
v-if="counter == 1"
|
v-if="counter == 1"
|
||||||
:name="
|
:name="deleteCartItemIsPending ? 'svg-spinners:3-dots-bounce' : 'bi:trash'"
|
||||||
deleteCartItemIsPending
|
|
||||||
? 'svg-spinners:3-dots-bounce'
|
|
||||||
: 'bi:trash'
|
|
||||||
"
|
|
||||||
class="**:fill-red-700"
|
class="**:fill-red-700"
|
||||||
/>
|
/>
|
||||||
<Icon v-else name="bi:dash" class="**:stroke-slate-800" />
|
<Icon
|
||||||
|
v-else
|
||||||
|
name="bi:dash"
|
||||||
|
class="**:stroke-slate-800"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -9,19 +9,13 @@
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div class="flex flex-col gap-4 items-center justify-center relative z-20">
|
||||||
class="flex flex-col gap-4 items-center justify-center relative z-20"
|
<div class="flex items-center flex-col gap-8 pb-[10px] pt-[80px] lg:pt-[100px] lg:pb-[50px] justify-center">
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex items-center flex-col gap-8 pb-[10px] pt-[80px] lg:pt-[150px] lg:pb-[50px] justify-center"
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
src="/img/heymlz/heymlz-small-idle.gif"
|
src="/img/heymlz/heymlz-small-idle.gif"
|
||||||
class="size-[150px] lg:size-[220px] rounded-full drop-shadow-2xl"
|
class="size-[150px] lg:size-[220px] rounded-full drop-shadow-2xl"
|
||||||
/>
|
/>
|
||||||
<span
|
<span class="font-bold text-2xl lg:text-5xl text-gradient bg-gradient-to-l from-blue-500 to-blue-700">
|
||||||
class="font-bold text-2xl lg:text-5xl text-gradient bg-gradient-to-l from-blue-500 to-blue-700"
|
|
||||||
>
|
|
||||||
فروشگاه هی ملز
|
فروشگاه هی ملز
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,44 +24,49 @@
|
|||||||
class="w-full flex max-lg:flex-col justify-between py-[64px] max-lg:gap-16 container items-center lg:items-start relative z-20"
|
class="w-full flex max-lg:flex-col justify-between py-[64px] max-lg:gap-16 container items-center lg:items-start relative z-20"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-4 max-w-[300px]">
|
<div class="flex flex-col gap-4 max-w-[300px]">
|
||||||
<h3
|
<h3 class="font-bold text-lg xl:text-3xl max-lg:text-center text-white">
|
||||||
class="font-bold text-lg xl:text-3xl max-lg:text-center text-white"
|
|
||||||
>
|
|
||||||
با ما در ارتباط باشید...
|
با ما در ارتباط باشید...
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p
|
<p class="text-md font-thin leading-[175%] mt-4 max-lg:text-center text-slate-300 max-lg:text-xs">
|
||||||
class="text-md font-thin leading-[175%] mt-4 max-lg:text-center text-slate-300 max-lg:text-xs"
|
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنگی با تولید سادگی نامفهوم از صنعت چاپ و با
|
||||||
>
|
استفاده از طراحان گرافیک است. چاپگرها
|
||||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنگی با
|
|
||||||
تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان
|
|
||||||
گرافیک است. چاپگرها
|
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div class="flex items-center gap-4 mt-6 max-lg:justify-center">
|
||||||
class="flex items-center gap-4 mt-6 max-lg:justify-center"
|
<NuxtLink
|
||||||
>
|
to="#"
|
||||||
<NuxtLink to="#" class="flex-center size-[1.5rem]">
|
class="flex-center size-[1.5rem]"
|
||||||
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name="ci:instagram"
|
name="ci:instagram"
|
||||||
class="**:fill-white"
|
class="**:fill-white"
|
||||||
size="24"
|
size="24"
|
||||||
/>
|
/>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink to="#" class="flex-center size-[1.5rem]">
|
<NuxtLink
|
||||||
|
to="#"
|
||||||
|
class="flex-center size-[1.5rem]"
|
||||||
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name="ci:facebook"
|
name="ci:facebook"
|
||||||
class="**:fill-white **:stroke-white"
|
class="**:fill-white **:stroke-white"
|
||||||
size="20"
|
size="20"
|
||||||
/>
|
/>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink to="#" class="flex-center size-[1.5rem]">
|
<NuxtLink
|
||||||
|
to="#"
|
||||||
|
class="flex-center size-[1.5rem]"
|
||||||
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name="ci:tiktok"
|
name="ci:tiktok"
|
||||||
class="**:fill-white **:stroke-white"
|
class="**:fill-white **:stroke-white"
|
||||||
size="20"
|
size="20"
|
||||||
/>
|
/>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink to="#" class="flex-center size-[1.5rem]">
|
<NuxtLink
|
||||||
|
to="#"
|
||||||
|
class="flex-center size-[1.5rem]"
|
||||||
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name="ci:youtube"
|
name="ci:youtube"
|
||||||
class="**:fill-white"
|
class="**:fill-white"
|
||||||
@@ -79,9 +78,7 @@
|
|||||||
<div class="flex justify-center lg:justify-end flex-1">
|
<div class="flex justify-center lg:justify-end flex-1">
|
||||||
<div class="flex flex-col gap-6 max-lg:text-center">
|
<div class="flex flex-col gap-6 max-lg:text-center">
|
||||||
<h3 class="font-bold text-white">لینک های مفید</h3>
|
<h3 class="font-bold text-white">لینک های مفید</h3>
|
||||||
<ul
|
<ul class="flex flex-col gap-4 font-thin text-slate-300 max-lg:text-xs">
|
||||||
class="flex flex-col gap-4 font-thin text-slate-300 max-lg:text-xs"
|
|
||||||
>
|
|
||||||
<li>از طراحان گرافیک است</li>
|
<li>از طراحان گرافیک است</li>
|
||||||
<li>تولید نامفهوم</li>
|
<li>تولید نامفهوم</li>
|
||||||
<li>ستون و سطرآنچنان که لازم</li>
|
<li>ستون و سطرآنچنان که لازم</li>
|
||||||
@@ -92,9 +89,7 @@
|
|||||||
<div class="flex justify-end flex-1">
|
<div class="flex justify-end flex-1">
|
||||||
<div class="flex flex-col gap-6 max-lg:text-center">
|
<div class="flex flex-col gap-6 max-lg:text-center">
|
||||||
<h3 class="font-bold text-white">لینک های مفید</h3>
|
<h3 class="font-bold text-white">لینک های مفید</h3>
|
||||||
<ul
|
<ul class="flex flex-col gap-4 font-thin text-slate-300 max-lg:text-xs">
|
||||||
class="flex flex-col gap-4 font-thin text-slate-300 max-lg:text-xs"
|
|
||||||
>
|
|
||||||
<li>از طراحان گرافیک است</li>
|
<li>از طراحان گرافیک است</li>
|
||||||
<li>تولید نامفهوم</li>
|
<li>تولید نامفهوم</li>
|
||||||
<li>ستون و سطرآنچنان که لازم</li>
|
<li>ستون و سطرآنچنان که لازم</li>
|
||||||
@@ -104,12 +99,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end flex-1">
|
<div class="flex justify-end flex-1">
|
||||||
<div class="flex flex-col gap-6 max-lg:text-center">
|
<div class="flex flex-col gap-6 max-lg:text-center">
|
||||||
<h3 class="font-bold w-full text-white">
|
<h3 class="font-bold w-full text-white">لینک های مفید</h3>
|
||||||
لینک های مفید
|
<ul class="flex flex-col gap-4 font-thin text-slate-300 max-lg:text-xs">
|
||||||
</h3>
|
|
||||||
<ul
|
|
||||||
class="flex flex-col gap-4 font-thin text-slate-300 max-lg:text-xs"
|
|
||||||
>
|
|
||||||
<li>از طراحان گرافیک است</li>
|
<li>از طراحان گرافیک است</li>
|
||||||
<li>تولید نامفهوم</li>
|
<li>تولید نامفهوم</li>
|
||||||
<li>ستون و سطرآنچنان که لازم</li>
|
<li>ستون و سطرآنچنان که لازم</li>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const classes = computed(() => {
|
|||||||
"input-solid": variant.value === "solid",
|
"input-solid": variant.value === "solid",
|
||||||
"input-outlined": variant.value === "outlined",
|
"input-outlined": variant.value === "outlined",
|
||||||
"input-effects": !error.value,
|
"input-effects": !error.value,
|
||||||
[variant.value === "solid" ? "input-solid-error" : "input-outlined-error"]: error.value,
|
[variant.value === "solid" ? "!input-solid-error" : "!input-outlined-error"]: error.value,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,17 +40,17 @@ const statusIcon = computed(() => {
|
|||||||
case "success":
|
case "success":
|
||||||
return {
|
return {
|
||||||
name: "duo-icons:check-circle",
|
name: "duo-icons:check-circle",
|
||||||
class: "**:fill-success-500 [filter:drop-shadow(0_0_10px_var(--color-success-500))]",
|
class: "**:fill-success-500 [filter:drop-shadow(0_0_20px_var(--color-success-500))]",
|
||||||
};
|
};
|
||||||
case "error":
|
case "error":
|
||||||
return {
|
return {
|
||||||
name: "duo-icons:alert-triangle",
|
name: "duo-icons:alert-triangle",
|
||||||
class: "**:fill-danger-500 [filter:drop-shadow(0_0_10px_var(--color-danger-500))]",
|
class: "**:fill-danger-500 [filter:drop-shadow(0_0_20px_var(--color-danger-500))]",
|
||||||
};
|
};
|
||||||
case "info":
|
case "info":
|
||||||
return {
|
return {
|
||||||
name: "duo-icons:info",
|
name: "duo-icons:info",
|
||||||
class: "**:fill-cyan-500 [filter:drop-shadow(0_0_10px_var(--color-cyan-500))]",
|
class: "**:fill-cyan-500 [filter:drop-shadow(0_0_20px_var(--color-cyan-500))]",
|
||||||
};
|
};
|
||||||
case "warning":
|
case "warning":
|
||||||
return {
|
return {
|
||||||
@@ -88,20 +88,25 @@ onMounted(() => {
|
|||||||
:duration="options.duration ?? 4000"
|
:duration="options.duration ?? 4000"
|
||||||
@swipeEnd="onSwipeEnd"
|
@swipeEnd="onSwipeEnd"
|
||||||
v-model:open="open"
|
v-model:open="open"
|
||||||
class="w-full bg-white shadow-md justify-items-start shadow-black/3 border-t-[0.5px] border-slate-200 p-4 grid [grid-template-areas:_'title_action'_'description_action'] grid-cols-[auto_max-content] gap-x-[15px] items-center data-[state=open]:animate-toast-in data-[state=closed]:animate-toast-hide data-[swipe=move]:translate-x-[var(--reka-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=cancel]:transition-[transform_200ms_ease-out] data-[swipe=end]:animate-toast-out"
|
class="w-full bg-white shadow-md justify-items-start shadow-black/3 border-[0.5px] flex flex-col border-slate-300 p-4 gap-x-[15px] items-center data-[state=open]:animate-toast-in data-[state=closed]:animate-toast-hide data-[swipe=move]:translate-x-[var(--reka-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=cancel]:transition-[transform_200ms_ease-out] data-[swipe=end]:animate-toast-out"
|
||||||
:class="options.description ? 'rounded-150' : 'rounded-full'"
|
:class="options.description ? 'rounded-150' : 'rounded-full'"
|
||||||
>
|
>
|
||||||
<ToastTitle
|
<ToastTitle
|
||||||
:class="[{ 'mb-1.5': options.description }]"
|
:class="[{ 'mb-1': options.description }]"
|
||||||
class="w-full justify-items-start [grid-area:_title] font-medium text-slate-600 text-sm flex items-center justify-between gap-2"
|
class="w-full justify-items-start font-medium text-slate-600 text-sm flex items-center justify-between gap-2"
|
||||||
>
|
>
|
||||||
<Icon :name="statusIcon.name" :class="statusIcon.class" size="24" />
|
<Icon
|
||||||
<span class="text-start -me-2">{{ message }}</span>
|
:name="statusIcon.name"
|
||||||
|
:class="statusIcon.class"
|
||||||
|
size="24"
|
||||||
|
/>
|
||||||
|
<span class="text-start">{{ message }}</span>
|
||||||
</ToastTitle>
|
</ToastTitle>
|
||||||
<ToastDescription v-if="options.description" as-child>
|
<ToastDescription
|
||||||
<div
|
v-if="options.description"
|
||||||
class="[grid-area:_description] m-0 mr-8 text-slate-500 typo-p-sm text-start"
|
as-child
|
||||||
>
|
>
|
||||||
|
<div class="text-slate-400 typo-p-xs font-medium flex items-center justify-end w-full">
|
||||||
{{ options.description }}
|
{{ options.description }}
|
||||||
</div>
|
</div>
|
||||||
</ToastDescription>
|
</ToastDescription>
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ const route = useRoute();
|
|||||||
// computed
|
// computed
|
||||||
|
|
||||||
const pageTitle = computed(() => route.meta.pageTitle);
|
const pageTitle = computed(() => route.meta.pageTitle);
|
||||||
const prevPage = computed(
|
const prevPage = computed(() => route.meta.prevPage as { name: string; label: string } | undefined);
|
||||||
() => route.meta.prevPage as { name: string; label: string } | undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
// queries
|
// queries
|
||||||
|
|
||||||
@@ -20,9 +18,7 @@ await suspense();
|
|||||||
|
|
||||||
// computed
|
// computed
|
||||||
|
|
||||||
const hasCartItem = computed(
|
const hasCartItem = computed(() => !!cart.value && cart.value.items.length! > 0);
|
||||||
() => !!cart.value && cart.value.items.length! > 0
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -32,20 +28,16 @@ const hasCartItem = computed(
|
|||||||
>
|
>
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<main
|
<main class="w-full overflow-x-hidden flex flex-col gap-[5rem] lg:max-w-[85vw]">
|
||||||
class="w-full overflow-x-hidden flex flex-col gap-[5rem] lg:max-w-[85vw]"
|
|
||||||
>
|
|
||||||
<div class="w-full flex flex-col container">
|
<div class="w-full flex flex-col container">
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-center justify-center py-[3.5rem] lg:py-[5rem] gap-5 lg:gap-0 lg:flex-row"
|
class="flex flex-col items-center justify-center py-[3.5rem] lg:py-[5rem] gap-5 lg:gap-0 lg:flex-row"
|
||||||
>
|
>
|
||||||
<div
|
<div class="flex items-center justify-start w-full lg:w-3/12">
|
||||||
class="flex items-center justify-start w-full lg:w-3/12"
|
|
||||||
>
|
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="prevPage"
|
v-if="prevPage"
|
||||||
:to="{ name: prevPage?.name }"
|
:to="{ name: prevPage?.name }"
|
||||||
class="flex items-center gap-2 text-sm lg:text-[1rem]"
|
class="flex items-center gap-2 text-sm lg:text-[1rem] font-medium"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name="bi:arrow-right"
|
name="bi:arrow-right"
|
||||||
@@ -57,9 +49,7 @@ const hasCartItem = computed(
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1
|
<h1 class="w-full text-center lg:w-6/12 typo-h-5 lg:typo-h-4">
|
||||||
class="w-full text-center lg:w-6/12 typo-h-5 lg:typo-h-4"
|
|
||||||
>
|
|
||||||
{{ pageTitle }}
|
{{ pageTitle }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export default defineNuxtRouteMiddleware((to, from) => {
|
||||||
|
if (to.path !== from.path && process.client) {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -80,18 +80,12 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col w-full gap-5">
|
<div class="flex flex-col w-full gap-5">
|
||||||
<div
|
<div class="flex flex-col items-center w-full gap-4 p-4 border border-slate-200 rounded-xl bg-gray-50">
|
||||||
class="flex flex-col items-center w-full gap-4 p-4 border border-gray-300 rounded-xl bg-gray-50"
|
<span class="flex items-center justify-start w-full text-[1.125rem] font-semibold text-gray-900">
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="flex items-center justify-start w-full text-[1.125rem] font-semibold text-gray-900"
|
|
||||||
>
|
|
||||||
روش پرداخت
|
روش پرداخت
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div
|
<div class="w-full grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
|
||||||
class="w-full grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4"
|
|
||||||
>
|
|
||||||
<Gateway
|
<Gateway
|
||||||
v-for="(gateway, index) in paymentGateways"
|
v-for="(gateway, index) in paymentGateways"
|
||||||
:index="index"
|
:index="index"
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
// imports
|
// imports
|
||||||
|
|
||||||
import useGetAllAddress from "~/composables/api/account/useGetAllAddress";
|
import useGetAllAddress from "~/composables/api/account/useGetAllAddress";
|
||||||
import useSetOrderAddress from "~/composables/api/orders/useSetOrderAddress";
|
|
||||||
import { useToast } from "~/composables/global/useToast";
|
import { useToast } from "~/composables/global/useToast";
|
||||||
import { QUERY_KEYS } from "~/constants";
|
import useGetCartOrders from "~/composables/api/orders/useGetCartOrders";
|
||||||
|
|
||||||
// meta
|
// meta
|
||||||
|
|
||||||
@@ -29,10 +28,9 @@ type DeliveryData = {
|
|||||||
|
|
||||||
// queries
|
// queries
|
||||||
|
|
||||||
const { data: addresses, isLoading: addressesIsLoading } = useGetAllAddress();
|
const { data: cart, isLoading: cartIsLoading } = useGetCartOrders();
|
||||||
|
|
||||||
const { mutateAsync: setOrderAddress, isPending: setOrderAddressIsPending } =
|
const { data: addresses, isLoading: addressesIsLoading } = useGetAllAddress();
|
||||||
useSetOrderAddress();
|
|
||||||
|
|
||||||
// computed
|
// computed
|
||||||
|
|
||||||
@@ -53,41 +51,6 @@ const deliveryData = ref<DeliveryData>({
|
|||||||
tipax: false,
|
tipax: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// methods
|
|
||||||
|
|
||||||
const handleSelectAddress = (address: Address) => {
|
|
||||||
deliveryData.value.address = { ...address };
|
|
||||||
};
|
|
||||||
|
|
||||||
// watch
|
|
||||||
|
|
||||||
whenever(
|
|
||||||
() => deliveryData.value.address,
|
|
||||||
(nv) => {
|
|
||||||
setOrderAddress(
|
|
||||||
{ address_id: nv.id! },
|
|
||||||
{
|
|
||||||
onSettled: () => {
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: [QUERY_KEYS.addresses],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
addToast({
|
|
||||||
message: "در انتخاب آدرس خطایی رخ داد",
|
|
||||||
options: {
|
|
||||||
description: "لطفا مجدد تلاش کنید",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -95,17 +58,20 @@ whenever(
|
|||||||
<AddressItem />
|
<AddressItem />
|
||||||
<div class="flex flex-col w-full gap-6">
|
<div class="flex flex-col w-full gap-6">
|
||||||
<div class="flex items-center gap-3 py-3">
|
<div class="flex items-center gap-3 py-3">
|
||||||
<NuxtImg src="/img/location.gif" class="size-12 pb-1 -mr-3" />
|
<NuxtImg
|
||||||
<span class="typo-sub-h-xl -mr-3"> آدرس های شما </span>
|
src="/img/location.gif"
|
||||||
<Icon
|
class="size-12 pb-1 -mr-3"
|
||||||
name="svg-spinners:180-ring-with-bg"
|
|
||||||
size="20"
|
|
||||||
v-if="setOrderAddressIsPending"
|
|
||||||
class="pb-0.5"
|
|
||||||
/>
|
/>
|
||||||
|
<span class="typo-sub-h-xl -mr-3"> آدرس های شما </span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="addressesIsLoading" class="flex flex-col gap-6 w-full">
|
<div
|
||||||
<Skeleton v-for="i in 3" class="w-full !h-[8rem] !rounded-xl" />
|
v-if="addressesIsLoading"
|
||||||
|
class="flex flex-col gap-6 w-full"
|
||||||
|
>
|
||||||
|
<Skeleton
|
||||||
|
v-for="i in 3"
|
||||||
|
class="w-full !h-[8rem] !rounded-xl"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div
|
<div
|
||||||
@@ -120,34 +86,28 @@ whenever(
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="flex flex-col gap-6 w-full">
|
<div
|
||||||
|
v-else
|
||||||
|
class="flex flex-col gap-6 w-full"
|
||||||
|
>
|
||||||
<AddressItem
|
<AddressItem
|
||||||
v-for="(address, index) in addresses"
|
v-for="(address, index) in addresses"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
:isSelected="address.id === cart?.address.id"
|
||||||
:address="address"
|
:address="address"
|
||||||
@select="handleSelectAddress"
|
|
||||||
:isSelected="address.id == deliveryData.address?.id"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="flex flex-col items-center w-full gap-4 my-3 p-4 border border-slate-200 rounded-xl bg-slate-50">
|
||||||
class="flex flex-col items-center w-full gap-4 my-3 p-4 border border-slate-200 rounded-xl bg-slate-50"
|
<span class="flex items-center justify-start w-full lg:text-[1.125rem] font-semibold text-slate-900">
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="flex items-center justify-start w-full lg:text-[1.125rem] font-semibold text-slate-900"
|
|
||||||
>
|
|
||||||
شیوه ارسال
|
شیوه ارسال
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<label
|
<label
|
||||||
@click="deliveryData.deliveryMethod.pishtaz = true"
|
@click="deliveryData.deliveryMethod.pishtaz = true"
|
||||||
:class="
|
:class="deliveryData.deliveryMethod.pishtaz ? 'ring-black ring-offset-2 ring-2' : ''"
|
||||||
deliveryData.deliveryMethod.pishtaz
|
|
||||||
? 'ring-black ring-offset-2 ring-2'
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
class="flex flex-col select-none w-full gap-2 p-3 transition-all border cursor-pointer delivery-option focus-within:ring-2 ring-blue-500 ring-offset-2 focus-within:border-blue-500 rounded-100 border-slate-200 bg-slate-50"
|
class="flex flex-col select-none w-full gap-2 p-3 transition-all border cursor-pointer delivery-option focus-within:ring-2 ring-blue-500 ring-offset-2 focus-within:border-blue-500 rounded-100 border-slate-200 bg-slate-50"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between w-full">
|
<div class="flex items-center justify-between w-full">
|
||||||
@@ -161,15 +121,10 @@ whenever(
|
|||||||
class="size-6 my-auto bg-white text-sm ms-1 flex items-center justify-center shadow-xl rounded-full transition-transform translate-x-0.5 will-change-transform data-[state=checked]:-translate-x-[68%]"
|
class="size-6 my-auto bg-white text-sm ms-1 flex items-center justify-center shadow-xl rounded-full transition-transform translate-x-0.5 will-change-transform data-[state=checked]:-translate-x-[68%]"
|
||||||
/>
|
/>
|
||||||
</SwitchRoot>
|
</SwitchRoot>
|
||||||
<span
|
<span class="w-full text-slate-800 text-sm lg:text-[1rem]">پست پیشتاز</span>
|
||||||
class="w-full text-slate-800 text-sm lg:text-[1rem]"
|
|
||||||
>پست پیشتاز</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="text-slate-800 text-sm lg:text-[1rem]">
|
<span class="text-slate-800 text-sm lg:text-[1rem]"> ۱۵۰٬۰۰۰ تومان </span>
|
||||||
۱۵۰٬۰۰۰ تومان
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -185,14 +140,10 @@ whenever(
|
|||||||
class="size-6 my-auto bg-white text-sm ms-1 flex items-center justify-center shadow-xl rounded-full transition-transform translate-x-0.5 will-change-transform data-[state=checked]:-translate-x-[68%]"
|
class="size-6 my-auto bg-white text-sm ms-1 flex items-center justify-center shadow-xl rounded-full transition-transform translate-x-0.5 will-change-transform data-[state=checked]:-translate-x-[68%]"
|
||||||
/>
|
/>
|
||||||
</SwitchRoot>
|
</SwitchRoot>
|
||||||
<span class="w-full text-slate-800 text-sm lg:text-[1rem]"
|
<span class="w-full text-slate-800 text-sm lg:text-[1rem]">تیپاکس</span>
|
||||||
>تیپاکس</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="text-slate-800 text-sm lg:text-[1rem]">
|
<span class="text-slate-800 text-sm lg:text-[1rem]"> ۱۵۰٬۰۰۰ تومان </span>
|
||||||
۱۵۰٬۰۰۰ تومان
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,7 @@
|
|||||||
import useVuelidate from "@vuelidate/core";
|
import useVuelidate from "@vuelidate/core";
|
||||||
import { helpers, required, minLength, email } from "@vuelidate/validators";
|
import { helpers, required, minLength, email } from "@vuelidate/validators";
|
||||||
import useGetAccount from "~/composables/api/account/useGetAccount";
|
import useGetAccount from "~/composables/api/account/useGetAccount";
|
||||||
import useUpdateAccount, {
|
import useUpdateAccount, { type UpdateAccountRequest } from "~/composables/api/account/useUpdateAccount";
|
||||||
type UpdateAccountRequest,
|
|
||||||
} from "~/composables/api/account/useUpdateAccount";
|
|
||||||
import { useObjectTrack } from "~/composables/global/useObjectTrack";
|
import { useObjectTrack } from "~/composables/global/useObjectTrack";
|
||||||
import { useToast } from "~/composables/global/useToast";
|
import { useToast } from "~/composables/global/useToast";
|
||||||
import { QUERY_KEYS } from "~/constants";
|
import { QUERY_KEYS } from "~/constants";
|
||||||
@@ -40,18 +38,11 @@ const profilePictureModalIsShow = ref(false);
|
|||||||
|
|
||||||
const { isNotEqual, clear: clearObjectTracker } = useObjectTrack(personalData);
|
const { isNotEqual, clear: clearObjectTracker } = useObjectTrack(personalData);
|
||||||
|
|
||||||
const alises = ref([
|
const alises = ref(["شکارچی", "آیفون باز", "خوش سلیقه", "دست و دلباز", "چرم باز"]);
|
||||||
"شکارچی",
|
|
||||||
"آیفون باز",
|
|
||||||
"خوش سلیقه",
|
|
||||||
"دست و دلباز",
|
|
||||||
"چرم باز",
|
|
||||||
]);
|
|
||||||
|
|
||||||
// queries
|
// queries
|
||||||
|
|
||||||
const { mutateAsync: updateAccount, isPending: updateAccountIsPending } =
|
const { mutateAsync: updateAccount, isPending: updateAccountIsPending } = useUpdateAccount();
|
||||||
useUpdateAccount();
|
|
||||||
|
|
||||||
// computed
|
// computed
|
||||||
|
|
||||||
@@ -59,52 +50,28 @@ const formRules = computed(() => {
|
|||||||
return {
|
return {
|
||||||
first_name: {
|
first_name: {
|
||||||
required: helpers.withMessage("فیلد نام الزامی می باشد", required),
|
required: helpers.withMessage("فیلد نام الزامی می باشد", required),
|
||||||
minLength: helpers.withMessage(
|
minLength: helpers.withMessage("فیلد نام حداقل ۳ کرکتر می باشد", minLength(3)),
|
||||||
"فیلد نام حداقل ۳ کرکتر می باشد",
|
|
||||||
minLength(3)
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
last_name: {
|
last_name: {
|
||||||
required: helpers.withMessage(
|
required: helpers.withMessage("فیلد نام خانوادگی الزامی می باشد", required),
|
||||||
"فیلد نام خانوادگی الزامی می باشد",
|
minLength: helpers.withMessage("فیلد نام خانوادگی حداقل ۳ کرکتر می باشد", minLength(3)),
|
||||||
required
|
|
||||||
),
|
|
||||||
minLength: helpers.withMessage(
|
|
||||||
"فیلد نام خانوادگی حداقل ۳ کرکتر می باشد",
|
|
||||||
minLength(3)
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
phone: {
|
phone: {
|
||||||
required: helpers.withMessage(
|
required: helpers.withMessage("فیلد شماره تلفن الزامی می باشد", required),
|
||||||
"فیلد شماره تلفن الزامی می باشد",
|
|
||||||
required
|
|
||||||
),
|
|
||||||
phoneValidator: helpers.withMessage(
|
phoneValidator: helpers.withMessage(
|
||||||
"شماره تلفن وارد شده معتبر نمی باشد",
|
"شماره تلفن وارد شده معتبر نمی باشد",
|
||||||
helpers.regex(/^0?[1-9][0-9]{9}$/)
|
helpers.regex(/^0?[1-9][0-9]{9}$/)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
gender: {
|
gender: {
|
||||||
required: helpers.withMessage(
|
required: helpers.withMessage("فیلد جنسیت الزامی می باشد", required),
|
||||||
"فیلد جنسیت الزامی می باشد",
|
|
||||||
required
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
required: helpers.withMessage(
|
required: helpers.withMessage("فیلد حساب الکترونیکی الزامی می باشد", required),
|
||||||
"فیلد حساب الکترونیکی الزامی می باشد",
|
email: helpers.withMessage("حساب الکترونیکی وارد شده معتبر نمی باشد", email),
|
||||||
required
|
|
||||||
),
|
|
||||||
email: helpers.withMessage(
|
|
||||||
"حساب الکترونیکی وارد شده معتبر نمی باشد",
|
|
||||||
email
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
birth_date: {
|
birth_date: {
|
||||||
required: helpers.withMessage(
|
required: helpers.withMessage("فیلد تاریخ تولد الزامی می باشد", required),
|
||||||
"فیلد تاریخ تولد الزامی می باشد",
|
|
||||||
required
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -167,18 +134,14 @@ const handleSubmit = (withValidation: boolean) => {
|
|||||||
<div
|
<div
|
||||||
class="w-full flex flex-col lg:flex-row items-center max-lg:gap-5 lg:justify-between border p-6 rounded-xl border-slate-200"
|
class="w-full flex flex-col lg:flex-row items-center max-lg:gap-5 lg:justify-between border p-6 rounded-xl border-slate-200"
|
||||||
>
|
>
|
||||||
<div
|
<div class="flex items-center justify-start gap-5 w-full lg:w-8/12">
|
||||||
class="flex items-center justify-start gap-5 w-full lg:w-8/12"
|
|
||||||
>
|
|
||||||
<div class="relative shrink-0 rounded-full flex-center">
|
<div class="relative shrink-0 rounded-full flex-center">
|
||||||
<Avatar
|
<Avatar
|
||||||
class="!size-20 lg:!size-32"
|
class="!size-20 lg:!size-32"
|
||||||
:src="account!.profile_photo"
|
:src="account!.profile_photo"
|
||||||
:alt="
|
:alt="
|
||||||
account?.first_name && account?.last_name
|
account?.first_name && account?.last_name
|
||||||
? `${account?.first_name.charAt(
|
? `${account?.first_name.charAt(0)} ${account?.last_name.charAt(0)}`
|
||||||
0
|
|
||||||
)} ${account?.last_name.charAt(0)}`
|
|
||||||
: 'بدون نام کاربری'
|
: 'بدون نام کاربری'
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
@@ -192,18 +155,12 @@ const handleSubmit = (withValidation: boolean) => {
|
|||||||
|
|
||||||
<div class="flex flex-col gap-2 lg:gap-3">
|
<div class="flex flex-col gap-2 lg:gap-3">
|
||||||
<span class="typo-sub-h-md lg:typo-sub-h-lg"
|
<span class="typo-sub-h-md lg:typo-sub-h-lg"
|
||||||
>{{ account?.first_name }}
|
>{{ account?.first_name }} {{ account?.last_name }}</span
|
||||||
{{ account?.last_name }}</span
|
|
||||||
>
|
>
|
||||||
<span
|
<span class="typo-sub-h-xs lg:typo-sub-h-sm !font-light text-slate-600 leading-[200%]">
|
||||||
class="typo-sub-h-xs lg:typo-sub-h-sm !font-light text-slate-600 leading-[200%]"
|
با اولین خریدتون هوش مصنوعی وبسایتمون واستون یک بایوگرافی درست میکنه :)
|
||||||
>
|
|
||||||
با اولین خریدتون هوش مصنوعی وبسایتمون واستون یک
|
|
||||||
بایوگرافی درست میکنه :)
|
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div class="flex-center border border-yellow-500 pe-3.5 ps-1 w-max rounded-full">
|
||||||
class="flex-center border border-yellow-500 pe-3.5 ps-1 w-max rounded-full"
|
|
||||||
>
|
|
||||||
<div class="rounded-full p-1.5 lg:p-2">
|
<div class="rounded-full p-1.5 lg:p-2">
|
||||||
<Icon
|
<Icon
|
||||||
name="bi:patch-check"
|
name="bi:patch-check"
|
||||||
@@ -211,26 +168,20 @@ const handleSubmit = (withValidation: boolean) => {
|
|||||||
size="20"
|
size="20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-[10px] lg:text-xs text-yellow-500"
|
<span class="text-[10px] lg:text-xs text-yellow-500">جزو ۳ مشتری برتر</span>
|
||||||
>جزو ۳ مشتری برتر</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col items-start gap-3 w-full lg:w-4/12">
|
<div class="flex flex-col items-start gap-3 w-full lg:w-4/12">
|
||||||
<span class="typo-sub-h-md lg:typo-sub-h-lg"
|
<span class="typo-sub-h-md lg:typo-sub-h-lg">لقب های شما</span>
|
||||||
>لقب های شما</span
|
|
||||||
>
|
|
||||||
<span class="flex w-full flex-wrap gap-2">
|
<span class="flex w-full flex-wrap gap-2">
|
||||||
<span
|
<span
|
||||||
v-for="(alise, index) in alises"
|
v-for="(alise, index) in alises"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="flex-center bg-slate-50 border border-slate-200 py-1.5 lg:py-2 px-3 w-max rounded-full"
|
class="flex-center bg-slate-50 border border-slate-200 py-1.5 lg:py-2 px-3 w-max rounded-full"
|
||||||
>
|
>
|
||||||
<span class="text-[10px] lg:text-xs text-black">{{
|
<span class="text-[10px] lg:text-xs text-black">{{ alise }}</span>
|
||||||
alise
|
|
||||||
}}</span>
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -251,9 +202,7 @@ const handleSubmit = (withValidation: boolean) => {
|
|||||||
<span v-else> ثبت تغییرات </span>
|
<span v-else> ثبت تغییرات </span>
|
||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div class="w-full grid grid-cols-1 lg:grid-cols-2 gap-x-3 gap-y-5">
|
||||||
class="w-full grid grid-cols-1 lg:grid-cols-2 gap-x-3 gap-y-5"
|
|
||||||
>
|
|
||||||
<DataField
|
<DataField
|
||||||
id="personal-data-name"
|
id="personal-data-name"
|
||||||
label="نام"
|
label="نام"
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB |
Vendored
+4
-19
@@ -109,10 +109,7 @@ declare global {
|
|||||||
colors: string[];
|
colors: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type ProductListItem = Pick<
|
type ProductListItem = Pick<Product, "id" | "variants" | "name" | "rating" | "slug" | "category" | "colors">;
|
||||||
Product,
|
|
||||||
"id" | "variants" | "name" | "rating" | "slug" | "category" | "colors"
|
|
||||||
>;
|
|
||||||
|
|
||||||
type Article = {
|
type Article = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -193,13 +190,7 @@ declare global {
|
|||||||
id: number;
|
id: number;
|
||||||
count: number;
|
count: number;
|
||||||
images: string[];
|
images: string[];
|
||||||
status:
|
status: "ADMIN_PENDING" | "PENDING" | "POSTED" | "RECEIVED" | "CANCELED" | "REFUND";
|
||||||
| "ADMIN_PENDING"
|
|
||||||
| "PENDING"
|
|
||||||
| "POSTED"
|
|
||||||
| "RECEIVED"
|
|
||||||
| "CANCELED"
|
|
||||||
| "REFUND";
|
|
||||||
verbose_status: string;
|
verbose_status: string;
|
||||||
is_paid: boolean;
|
is_paid: boolean;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
@@ -248,6 +239,7 @@ declare global {
|
|||||||
cart_total: string;
|
cart_total: string;
|
||||||
tax: string;
|
tax: string;
|
||||||
final_price: string;
|
final_price: string;
|
||||||
|
address: Address;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ServerFile = {
|
type ServerFile = {
|
||||||
@@ -281,14 +273,7 @@ declare global {
|
|||||||
id: number;
|
id: number;
|
||||||
picture: string;
|
picture: string;
|
||||||
title: string;
|
title: string;
|
||||||
type:
|
type: "ZARINPAL" | "SEP" | "MELLAT" | "IDPAY" | "ZIBAL" | "BAHAMTA" | "BMI";
|
||||||
| "ZARINPAL"
|
|
||||||
| "SEP"
|
|
||||||
| "MELLAT"
|
|
||||||
| "IDPAY"
|
|
||||||
| "ZIBAL"
|
|
||||||
| "BAHAMTA"
|
|
||||||
| "BMI";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type Transaction = {
|
type Transaction = {
|
||||||
|
|||||||
Reference in New Issue
Block a user